Inhalte in der App von Rand zu Rand anzeigen lassen und Fenstereinblendungen in „Compose“ bearbeiten

Die Android-Plattform ist für das Zeichnen der System-UI wie der Statusleiste und der Navigationsleiste verantwortlich. Diese System-UI wird unabhängig davon angezeigt, welche App der Nutzer verwendet.

WindowInsets enthält Informationen zur System-UI, damit Ihre App im richtigen Bereich gezeichnet wird und Ihre Benutzeroberfläche nicht von der System-UI verdeckt wird.

Vollbildmodus verwenden, um hinter den Systemleisten zu zeichnen
Abbildung 1: Sie gehen von Rand zu Rand, um hinter den Systemleisten zu zeichnen.

Unter Android 14 (API-Level 34) und niedriger wird die Benutzeroberfläche Ihrer App standardmäßig nicht unter den Systemleisten und Displayausschnitten gezeichnet.

Unter Android 15 (API-Level 35) und höher wird Ihre App unter den Systemleisten und Displayausschnitten dargestellt, wenn sie auf SDK 35 ausgerichtet ist. Das führt zu einer besseren Nutzererfahrung und ermöglicht es Ihrer App, den gesamten verfügbaren Fensterbereich zu nutzen.

Wenn Inhalte hinter der System-UI angezeigt werden, wird dies als Bildschirmrand-zu-Bildschirmrand-Ansicht bezeichnet. Auf dieser Seite erfahren Sie mehr über die verschiedenen Arten von Einblendungen, wie Sie den gesamten Bildschirm nutzen und wie Sie die Einblendungs-APIs verwenden, um Ihre Benutzeroberfläche zu animieren und dafür zu sorgen, dass die Inhalte Ihrer App nicht von System-UI-Elementen verdeckt werden.

Grundlegendes zu Einblendungen

Wenn eine App den gesamten Bildschirm einnimmt, müssen Sie darauf achten, dass wichtige Inhalte und Interaktionen nicht von der System-UI verdeckt werden. Wenn eine Schaltfläche beispielsweise hinter der Navigationsleiste platziert ist, kann der Nutzer möglicherweise nicht darauf klicken.

Die Größe der System-UI und Informationen zur Platzierung werden über Einzüge angegeben.

Jeder Bereich der System-UI hat einen entsprechenden Einleger, der seine Größe und Position beschreibt. So geben Einzüge für die Statusleiste beispielsweise die Größe und Position der Statusleiste an, während Einzüge für die Navigationsleiste die Größe und Position der Navigationsleiste angeben. Jeder Art von Einleger werden vier Pixelabmessungen zugewiesen: oben, links, rechts und unten. Diese Abmessungen geben an, wie weit sich die System-UI von den entsprechenden Seiten des App-Fensters erstreckt. Um Überschneidungen mit dieser Art von System-UI zu vermeiden, muss die App-UI um diesen Betrag eingerückt sein.

Diese integrierten Android-Einblendungstypen sind über WindowInsets verfügbar:

WindowInsets.statusBars

Die Einblendungen, die die Statusleisten beschreiben. Das sind die oberen System-UI-Leisten mit Benachrichtigungssymbolen und anderen Indikatoren.

WindowInsets.statusBarsIgnoringVisibility

Die Statusleiste wird eingerückt, wenn sie sichtbar ist. Wenn die Statusleisten derzeit ausgeblendet sind (weil der immersive Vollbildmodus aktiviert ist), sind die Einzüge der Hauptstatusleiste leer.

WindowInsets.navigationBars

Die Einblendungen, in denen die Navigationsleisten beschrieben werden. Dies sind die System-UI-Leisten auf der linken, rechten oder unteren Seite des Geräts, die die Taskleiste oder die Navigationssymbole beschreiben. Diese können sich zur Laufzeit ändern, je nachdem, welche Navigationsmethode der Nutzer bevorzugt und wie er mit der Taskleiste interagiert.

WindowInsets.navigationBarsIgnoringVisibility

Die Navigationsleiste wird eingerückt, wenn sie sichtbar ist. Wenn die Navigationsleisten derzeit ausgeblendet sind (weil der immersive Vollbildmodus aktiviert ist), sind die Einzüge der Hauptnavigationsleiste leer.

WindowInsets.captionBar

Der Einschub, der die Fensterdekoration der System-UI beschreibt, wenn sich das Fenster in einem freiformigen Fenster befindet, z. B. die obere Titelleiste.

WindowInsets.captionBarIgnoringVisibility

Die Untertitelleiste wird eingeblendet, wenn sie sichtbar ist. Wenn die Untertitelleisten derzeit ausgeblendet sind, sind die Einzüge der Hauptuntertitelleiste leer.

WindowInsets.systemBars

Die Vereinigung der Systemleisten, einschließlich der Statusleisten, Navigationsleisten und Titelleisten.

WindowInsets.systemBarsIgnoringVisibility

Die Systemleiste wird eingeblendet, wenn sie sichtbar ist. Wenn die Systemleisten derzeit ausgeblendet sind (weil der immersive Vollbildmodus aktiviert ist), sind die Einzüge der Hauptsystemleiste leer.

WindowInsets.ime

Die Einzüge, die den Platz unten beschreiben, den die Softwaretastatur einnimmt.

WindowInsets.imeAnimationSource

Die Einblendungen, die den Platz beschreiben, den die Softwaretastatur vor der aktuellen Tastaturanimation belegt hat.

WindowInsets.imeAnimationTarget

Die Einzüge, die den Platz beschreiben, den die Softwaretastatur nach der aktuellen Tastaturanimation einnimmt.

WindowInsets.tappableElement

Eine Art von Einblendungen, die detailliertere Informationen zur Navigations-UI enthalten und den Bereich angeben, in dem „Tippen“ vom System und nicht von der App verarbeitet wird. Bei transparenten Navigationsleisten mit Gestennavigation können einige App-Elemente über die Systemnavigations-UI angetippt werden.

WindowInsets.tappableElementIgnoringVisibility

Die anklickbaren Elemente werden eingeblendet, wenn sie sichtbar sind. Wenn die anklickbaren Elemente derzeit ausgeblendet sind (z. B. weil der immersive Vollbildmodus aktiviert ist), sind die Einzüge der Hauptelemente leer.

WindowInsets.systemGestures

Die Einzüge, die die Anzahl der Einzüge darstellen, bei denen das System Touch-Gesten für die Navigation abfängt. Apps können die Verarbeitung einer begrenzten Anzahl dieser Touch-Gesten über Modifier.systemGestureExclusion manuell festlegen.

WindowInsets.mandatorySystemGestures

Ein Teil der Systemgesten, die immer vom System verarbeitet werden und die nicht über Modifier.systemGestureExclusion deaktiviert werden können.

WindowInsets.displayCutout

Die Einzüge, die den Abstand darstellen, der erforderlich ist, um Überschneidungen mit einem Displayausschnitt (Notch oder Loch) zu vermeiden.

WindowInsets.waterfall

Die Einzüge, die die gekrümmten Bereiche eines Kaskadenaufrufs darstellen. Ein Waterfall-Display hat an den Rändern des Displays gekrümmte Bereiche, in denen das Display an den Seiten des Geräts umgeschlagen wird.

Diese Typen werden in drei „sichere“ Einblendungstypen zusammengefasst, die dafür sorgen, dass Inhalte nicht verdeckt werden:

Diese „sicheren“ Einbettungstypen schützen Inhalte je nach zugrunde liegender Plattform auf unterschiedliche Weise:

  • Verwenden Sie WindowInsets.safeDrawing, um Inhalte zu schützen, die nicht unter der Benutzeroberfläche des Systems dargestellt werden sollen. Dies ist die häufigste Verwendung von Einblendungen: Sie verhindern das Zeichnen von Inhalten, die teilweise oder vollständig von der System-UI verdeckt werden.
  • Verwenden Sie WindowInsets.safeGestures, um Inhalte mit Touch-Gesten zu schützen. So wird verhindert, dass System-Gesten mit App-Gesten kollidieren, z. B. mit denen für Infofelder, Karussells oder in Spielen.
  • Verwenden Sie WindowInsets.safeContent als Kombination aus WindowInsets.safeDrawing und WindowInsets.safeGestures, damit sich Inhalte nicht überschneiden und es keine Überschneidungen bei Gesten gibt.

Einzüge einrichten

Wenn Sie Ihrer App die vollständige Kontrolle darüber geben möchten, wo Inhalte dargestellt werden, folgen Sie dieser Einrichtungsanleitung. Ohne diese Schritte wird in Ihrer App möglicherweise schwarz oder eine durchgehende Farbe hinter der System-UI gezeichnet oder die Animation erfolgt nicht synchron mit der Softwaretastatur.

  1. Richten Sie Ihre App auf SDK 35 oder höher aus, um unter Android 15 und höher Vollbildanzeigen zu erzwingen. Ihre App wird hinter der System-UI angezeigt. Sie können die Benutzeroberfläche Ihrer App anpassen, indem Sie Einblendungen verwenden.
  2. Optional können Sie enableEdgeToEdge() in Activity.onCreate() aufrufen, damit Ihre App in älteren Android-Versionen randlos angezeigt wird.
  3. Lege android:windowSoftInputMode="adjustResize" im AndroidManifest.xml-Eintrag deiner Aktivität fest. Mit dieser Einstellung kann Ihre App die Größe der Software-IME als Einzüge erhalten. So können Sie Inhalte entsprechend auffüllen und layouten, wenn die IME in Ihrer App erscheint und verschwindet.

    <!-- in your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

APIs erstellen

Sobald Ihre Aktivität die Verarbeitung aller Einblendungen übernommen hat, können Sie mit Compose APIs dafür sorgen, dass Inhalte nicht verdeckt und interaktive Elemente nicht mit der System-UI überlappen. Diese APIs synchronisieren auch das Layout Ihrer App mit den Änderungen am Insert.

So können Sie beispielsweise die Einblendungen auf den Inhalt Ihrer gesamten App anwenden:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

In diesem Snippet werden die Fenstereinzüge safeDrawing als Abstand um den gesamten Inhalt der App herum angewendet. Dadurch wird sichergestellt, dass sich interaktive Elemente nicht mit der System-UI überschneiden. Es bedeutet aber auch, dass keine App-Inhalte hinter der System-UI gezeichnet werden, um einen Rand-zu-Rand-Effekt zu erzielen. Damit Sie das gesamte Fenster optimal nutzen können, müssen Sie die Platzierung der Einblendungen auf Bildschirm- oder Komponentenebene optimieren.

Alle diese Einblendungstypen werden automatisch mit IME-Animationen animiert, die auf API 21 zurückportiert wurden. Außerdem werden alle Layouts, in denen diese Einzüge verwendet werden, automatisch animiert, wenn sich die Einzugswerte ändern.

Es gibt zwei Hauptmethoden, mit denen Sie diese Arten von Einzügen verwenden können, um Ihre zusammensetzbaren Layouts anzupassen: Padding-Modifikatoren und Modifikatoren für die Einzuggröße.

Ränder

Bei Modifier.windowInsetsPadding(windowInsets: WindowInsets) werden die angegebenen Fenstereinzüge als Abstand angewendet, genau wie bei Modifier.padding. Bei Modifier.windowInsetsPadding(WindowInsets.safeDrawing) werden die sicheren Zeichenbereiche beispielsweise als Abstand auf allen vier Seiten angewendet.

Außerdem gibt es mehrere integrierte Dienstprogrammmethoden für die gängigsten Arten von Einträgen. Modifier.safeDrawingPadding() ist eine solche Methode, die Modifier.windowInsetsPadding(WindowInsets.safeDrawing) entspricht. Für die anderen Arten von Einblendungen gibt es analoge Modifikatoren.

Modifikatoren für die Größe des Einzugs

Mit den folgenden Modifikatoren können Sie die Größe der Fensterausschnitte festlegen, indem Sie die Größe der Komponente auf die Größe der Ausschnitte festlegen:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Die Startseite von „windowInsets“ wird als Breite angewendet (z. B. Modifier.width).

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Die Endseite von „windowInsets“ wird als Breite angewendet (z. B. Modifier.width).

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Die Oberseite von „windowInsets“ wird als Höhe angewendet (z. B. Modifier.height).

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Die untere Seite von „windowInsets“ wird als Höhe angewendet (z. B. Modifier.height).

Diese Modifikatoren sind besonders nützlich, um die Größe eines Spacer festzulegen, das den Platz von Einzügen einnimmt:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Einblendung

Die Modifikatoren für den Abstand von Einzügen (windowInsetsPadding und Hilfselemente wie safeDrawingPadding) verbrauchen automatisch den Teil der Einzüge, der als Abstand angewendet wird. Wenn Sie tiefer in den Kompositionbaum eindringen, wissen verschachtelte Modifikatoren für den Abstand von Einzügen und die Größe von Einzügen, dass ein Teil der Einzüge bereits durch Modifikatoren für den Abstand von äußeren Einzügen belegt wurde. Sie vermeiden es, denselben Teil der Einzüge mehrmals zu verwenden, was zu zu viel zusätzlichem Platz führen würde.

Mithilfe von Modifikatoren für die Größe von Einzügen wird außerdem verhindert, dass derselbe Teil von Einzügen mehrmals verwendet wird, wenn Einzüge bereits verwendet wurden. Da sie ihre Größe jedoch direkt ändern, belegen sie selbst keine Einzüge.

Durch das Verschachteln von Padding-Modifizierern wird also automatisch die Größe des Paddings für jedes Composeable geändert.

Sehen wir uns das Beispiel mit LazyColumn noch einmal an. Hier wird die Größe von LazyColumn durch den Modifikator imePadding geändert. Innerhalb von LazyColumn hat das letzte Element die Höhe des unteren Endes der Systemleisten:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Wenn die IME geschlossen ist, wird mit dem imePadding()-Modifikator kein Abstand angewendet, da die IME keine Höhe hat. Da mit dem imePadding()-Modifikator kein Abstand angewendet wird, werden keine Einzüge verwendet und die Höhe der Spacer entspricht der Größe der Unterseite der Systemleisten.

Wenn die IME geöffnet wird, werden die Einzüge der IME animiert, um der Größe der IME zu entsprechen. Der Modifikator imePadding() beginnt dann, beim Öffnen der IME einen unteren Abstand anzuwenden, um die Größe von LazyColumn zu ändern. Sobald der imePadding()-Modifikator das untere Padding anwendet, wird auch diese Menge an Einzügen verbraucht. Daher beginnt die Höhe des Spacer zu sinken, da ein Teil des Abstands für die Systemleisten bereits durch den imePadding()-Modifikator angewendet wurde. Wenn der imePadding()-Modifikator einen Abstand nach unten anwendet, der größer als die Systemleisten ist, ist die Höhe von Spacer 0.

Wenn die IME geschlossen wird, werden die Änderungen in umgekehrter Reihenfolge ausgeführt: Die Spacer beginnt, sich von einer Höhe von null auszudehnen, sobald die imePadding() kleiner als die untere Seite der Systemleisten ist, bis die Spacer schließlich die Höhe der unteren Seite der Systemleisten erreicht, sobald die IME vollständig ausgeblendet ist.

Abbildung 2. Lazy-Spalte von Rand zu Rand mit TextField

Dieses Verhalten wird durch die Kommunikation zwischen allen windowInsetsPadding-Modifizierern erreicht und kann auf verschiedene andere Arten beeinflusst werden.

Modifier.consumeWindowInsets(insets: WindowInsets) verbraucht Einzüge auf dieselbe Weise wie Modifier.windowInsetsPadding, wendet sie aber nicht als Abstand an. Das ist in Kombination mit den Modifikatoren für die Größe des Einzugs nützlich, um Geschwistern anzugeben, dass bereits eine bestimmte Anzahl von Einzügen verwendet wurde:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) verhält sich sehr ähnlich wie die Version mit dem Argument WindowInsets, nimmt aber ein beliebiges PaddingValues zum Verbrauch an. Das ist nützlich, um Kinder darüber zu informieren, wenn Ränder oder Abstände durch einen anderen Mechanismus als die Modifikatoren für den Abstand zum Textfeld bereitgestellt werden, z. B. durch einen normalen Modifier.padding oder durch Abstände mit fester Höhe:

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

Wenn die Rohfenster-Inset-Werte ohne Verbrauch benötigt werden, verwenden Sie die WindowInsets-Werte direkt oder WindowInsets.asPaddingValues(), um eine PaddingValues der Inset-Werte zurückzugeben, die nicht vom Verbrauch betroffen sind. Aufgrund der folgenden Einschränkungen sollten Sie jedoch nach Möglichkeit die Modifikatoren für den Abstand und die Größe der Fenstereinzüge verwenden.

Ränder und Jetpack Compose-Phasen

Compose verwendet die zugrunde liegenden AndroidX-Kern-APIs, um Einblendungen zu aktualisieren und zu animieren. Dabei werden die zugrunde liegenden Plattform-APIs verwendet, die Einblendungen verwalten. Aufgrund dieses Plattformverhaltens haben Einblendungen eine besondere Beziehung zu den Phasen von Jetpack Compose.

Der Wert von „insets“ wird nach der Komposition, aber vor der Layoutphase aktualisiert. Das bedeutet, dass beim Lesen des Werts der Einblendungen in der Komposition in der Regel ein Wert der Einblendungen verwendet wird, der um einen Frame verzögert ist. Die auf dieser Seite beschriebenen integrierten Modifikatoren verzögern die Verwendung der Werte der Einzüge bis zur Layoutphase. So wird sichergestellt, dass die Einzugswerte im selben Frame verwendet werden, in dem sie aktualisiert werden.

IME-Animationen der Tastatur mit WindowInsets

Sie können Modifier.imeNestedScroll() auf einen scrollbaren Container anwenden, um die IME automatisch zu öffnen und zu schließen, wenn Sie zum Ende des Containers scrollen.

class WindowInsetsExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

Animation, in der ein UI-Element nach oben und unten scrollt, um Platz für eine Tastatur zu schaffen
Abbildung 3 IME-Animationen

Unterstützung für Einzüge bei Material 3-Komponenten

Für eine einfache Bedienung werden Einzüge bei vielen der integrierten Material 3-Kompositionen (androidx.compose.material3) automatisch verwaltet. Dabei wird berücksichtigt, wie die Kompositionen gemäß den Material-Spezifikationen in Ihrer App platziert werden.

Einfügen von Composeable-Elementen

Unten finden Sie eine Liste der Materialkomponenten, die Einzüge automatisch verarbeiten.

App-Leisten

Content-Container

Gerüst

Standardmäßig stellt Scaffold Einzüge als Parameter paddingValues bereit, die Sie verwenden können. Scaffold wendet die Einblendungen nicht auf Inhalte an. Dafür sind Sie selbst verantwortlich. So verwenden Sie diese Einzüge beispielsweise mit einem LazyColumn in einem Scaffold:

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

Standard-Einzüge überschreiben

Sie können den an das Composeable übergebenen Parameter windowInsets ändern, um das Verhalten des Composeable zu konfigurieren. Mit diesem Parameter kann ein anderer Fenstereinzug angewendet oder deaktiviert werden, indem eine leere Instanz übergeben wird: WindowInsets(0, 0, 0, 0).

Wenn Sie beispielsweise die Einzug-Verarbeitung für LargeTopAppBar deaktivieren möchten, setzen Sie den Parameter windowInsets auf eine leere Instanz:

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

Interoperabilität mit den Ansichts-Einblendungen

Möglicherweise müssen Sie die Standard-Einzüge überschreiben, wenn sich auf Ihrem Bildschirm sowohl Ansichten als auch Compose-Code in derselben Hierarchie befinden. In diesem Fall müssen Sie angeben, welche der beiden Seiten die Einleger verwenden und welche sie ignorieren soll.

Wenn Ihr äußerstes Layout beispielsweise ein Android-View-Layout ist, sollten Sie die Einzüge im View-System verwenden und für Compose ignorieren. Wenn Ihr äußerstes Layout ein Composeable ist, sollten Sie die Einzüge in Compose verwenden und die AndroidView-Composeables entsprechend ausrichten.

Standardmäßig werden von jedem ComposeView alle Inset-Assets auf der Verbrauchsebene WindowInsetsCompat verbraucht. Wenn Sie dieses Standardverhalten ändern möchten, setzen Sie ComposeView.consumeWindowInsets auf false.

Schutz der Systemleiste

Sobald Ihre App auf SDK 35 oder höher ausgerichtet ist, wird Edge-to-Edge erzwungen. Die Systemstatusleiste und die Navigationsleisten für Gesten sind transparent, die Navigationsleiste mit drei Schaltflächen ist jedoch durchscheinend.

Wenn Sie den standardmäßigen halbtransparenten Hintergrundschutz für die Navigation mit drei Schaltflächen entfernen möchten, setzen Sie Window.setNavigationBarContrastEnforced auf false.

Ressourcen