Fenstereinfügungen in Compose

Die Android-Plattform ist für das Zeichnen der System-UI verantwortlich, z. B. der Statusleiste und der Navigationsleiste. Diese System-UI wird unabhängig davon angezeigt, welche Anwendung der Nutzer verwendet. WindowInsets stellt Informationen zur System-UI bereit, damit Ihre App im richtigen Bereich gezeichnet wird und Ihre Benutzeroberfläche nicht von der System-UI verdeckt wird.

Von Rand zu Rand, um hinter den Systembalken zu zeichnen
Abbildung 1. Von Rand zu Rand, um hinter den Systemleisten zu zeichnen

Standardmäßig ist die Benutzeroberfläche Ihrer Anwendung darauf beschränkt, in der System-UI angeordnet zu werden, wie etwa in der Statusleiste und der Navigationsleiste. Dadurch wird sichergestellt, dass der Inhalt Ihrer App nicht von System-UI-Elementen verdeckt wird.

Wir empfehlen jedoch, für Apps die Anzeige in den Bereichen zu aktivieren, in denen auch die System-UI angezeigt wird. Dies sorgt für eine nahtlosere Nutzererfahrung und ermöglicht es Ihrer App, den verfügbaren Fensterbereich optimal zu nutzen. Dadurch können Apps auch zusammen mit der System-UI animiert werden, insbesondere beim Ein- und Ausblenden der Softwaretastatur.

Die Aktivierung der Anzeige in diesen Regionen und die Anzeige von Inhalten hinter der System-UI wird als Edge-to-Edge- bezeichnet. Auf dieser Seite erfahren Sie mehr über die verschiedenen Arten von Insets und wie Sie eine randlose Umstellung aktivieren. Außerdem erfahren Sie, wie Sie mit den eingefügten APIs Ihre UI animieren und vermeiden, dass Teile der App verdeckt werden.

Eingefügte Grundlagen

Wenn eine App Edge-to-Edge-Anwendungen entwickelt, müssen Sie dafür sorgen, dass wichtige Inhalte und Interaktionen nicht von der System-UI verdeckt werden. Wenn sich eine Schaltfläche beispielsweise hinter der Navigationsleiste befindet, kann der Nutzer sie möglicherweise nicht anklicken.

Die Größe der System-UI und Informationen zum Speicherort werden über Einsätze angegeben.

Jeder Teil der System-UI hat einen entsprechenden Einsatztyp, der seine Größe und seine Platzierung beschreibt. Einfügungen der Statusleiste beziehen sich beispielsweise auf die Größe und Position der Statusleiste, während die Einfügungen der Navigationsleiste die Größe und Position der Navigationsleiste angeben. Jeder Inset-Typ besteht aus vier Pixeldimensionen: oben, links, rechts und unten. Diese Dimensionen geben an, wie weit sich die System-UI von den entsprechenden Seiten des App-Fensters aus erstreckt. Um eine Überschneidung mit diesem Typ von System-UI zu vermeiden, muss die Anwendungs-UI daher in diesem Betrag eingesetzt werden.

Die folgenden integrierten Android-Einfügungstypen sind über WindowInsets verfügbar:

WindowInsets.statusBars

Die Einfügungen zur Beschreibung der Statusleisten. Dies sind die oberen System-UI-Leisten mit Benachrichtigungssymbolen und anderen Indikatoren.

WindowInsets.statusBarsIgnoringVisibility

Die Statusleisteneinfügungen für den Zeitpunkt, zu dem sie sichtbar sind. Wenn die Statusleisten derzeit ausgeblendet sind (aufgrund des immersiven Vollbildmodus), sind die Haupteinfügungen der Statusleiste leer. Diese Einfügungen sind jedoch nicht leer.

WindowInsets.navigationBars

Die Einfügungen zur Beschreibung der Navigationsleisten. Dies sind die System-UI-Leisten auf der linken, rechten oder unteren Seite des Geräts, die die Taskleiste oder Navigationssymbole beschreiben. Diese können sich zur Laufzeit je nach bevorzugter Navigationsmethode des Nutzers und Interaktion mit der Taskleiste ändern.

WindowInsets.navigationBarsIgnoringVisibility

Die Navigationsleisteneinfügungen für den Zeitpunkt, an dem sie sichtbar sind. Wenn die Navigationsleisten derzeit ausgeblendet sind (aufgrund des immersiven Vollbildmodus), sind die Einfügungen der Hauptnavigationsleiste leer, aber diese Einfügungen sind nicht leer.

WindowInsets.captionBar

Die Einfügung, die die Fenstergestaltung der System-UI beschreibt, wenn sie sich in einem freien Fenster befindet, z. B. in der oberen Titelleiste.

WindowInsets.captionBarIgnoringVisibility

Die Untertitelleiste wird eingeblendet, wenn sie sichtbar ist. Wenn die Untertitelleisten derzeit ausgeblendet sind, sind die Einfügungen der Hauptuntertitelleiste leer, aber diese Einfügungen sind nicht leer.

WindowInsets.systemBars

Die Gesamtheit der Systemleisten-Einfügungen, einschließlich der Statusleisten, Navigationsleisten und der Untertitelleiste.

WindowInsets.systemBarsIgnoringVisibility

Die Systemleisteneinfügungen für den Zeitpunkt, zu dem sie sichtbar sind. Wenn die Systemleisten derzeit ausgeblendet sind (aufgrund des immersiven Vollbildmodus), sind die Haupteinfügungen der Systemleiste leer. Diese Einfügungen sind jedoch nicht leer.

WindowInsets.ime

Die Einsätze, die angeben, wie viel Platz die Softwaretastatur unten einnimmt.

WindowInsets.imeAnimationSource

Die Einfügungen, die den Platz beschreiben, den die Softwaretastatur vor der aktuellen Tastaturanimation eingenommen hat.

WindowInsets.imeAnimationTarget

Die Einfügungen, die den Platz beschreiben, den die Softwaretastatur nach der aktuellen Tastaturanimation einnimmt.

WindowInsets.tappableElement

Eine Art von Einsätzen, die detailliertere Informationen zur Navigations-UI beschreiben und den Platz angeben, in dem „Antippen“ vom System und nicht von der App gehandhabt wird. Bei transparenten Navigationsleisten mit Gestensteuerung können einige App-Elemente über die Benutzeroberfläche der Systemsteuerung angetippt werden.

WindowInsets.tappableElementIgnoringVisibility

Die antippbaren Elementeinsätze, die sichtbar sind. Wenn die antippbaren Elemente derzeit ausgeblendet sind (aufgrund des immersiven Vollbildmodus), sind die Haupteinfügungen der antippbaren Elemente leer. Diese Einfügungen sind jedoch nicht leer.

WindowInsets.systemGestures

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

WindowInsets.mandatorySystemGestures

Eine Untergruppe der Systemgesten, die immer vom System ausgeführt werden und die nicht über Modifier.systemGestureExclusion deaktiviert werden können.

WindowInsets.displayCutout

Die Einsätze, die den Abstand darstellen, der erforderlich ist, um eine Überlappung mit einer Display-Aussparung (Kerbe oder Nadelloch) zu vermeiden.

WindowInsets.waterfall

Die Einsätze, die die gekrümmten Bereiche einer Wasserfalldarstellung darstellen. Bei einer Wasserfalldarstellung gibt es gebogene Bereiche entlang der Displayränder, an denen sich der Bildschirm entlang der Seiten des Geräts zusammenzieht.

Diese Typen werden in drei „sicheren“ Einsatztypen zusammengefasst, damit Inhalte nicht verdeckt werden:

Diese "sicheren" Einfügungstypen schützen Inhalte auf unterschiedliche Weise basierend auf den zugrunde liegenden Plattform-Einfügungen:

  • Mit WindowInsets.safeDrawing können Sie Inhalte schützen, die nicht unter einer System-UI angezeigt werden sollten. Dies ist die häufigste Verwendung von Einfügungen: Sie dient dazu, das Zeichnen von Inhalten zu verhindern, die teilweise oder vollständig von der System-UI verdeckt werden.
  • Verwenden Sie WindowInsets.safeGestures, um Inhalte mit Gesten zu schützen. Dadurch wird vermieden, dass System-Touch-Gesten mit App-Gesten in Konflikt geraten, z. B. für Ansichten am unteren Rand, Karussells oder in Spielen.
  • Verwende WindowInsets.safeContent in Kombination aus WindowInsets.safeDrawing und WindowInsets.safeGestures, damit Inhalte keine visuelle oder Gestenüberschneidung haben.

Einrichtung von Einsätzen

Führen Sie die folgenden Einrichtungsschritte aus, damit Ihre App die volle Kontrolle darüber hat, wo sie Inhalte abruft. Ohne diese Schritte zeichnet Ihre App möglicherweise schwarze oder Volltonfarben hinter der System-UI oder wird nicht synchron mit der Softwaretastatur animiert.

  1. Rufen Sie enableEdgeToEdge() in Activity.onCreate auf. Mit diesem Aufruf wird angefordert, dass Ihre App hinter der System-UI angezeigt wird. Ihre App steuert dann, wie diese Einfügungen verwendet werden, um die UI anzupassen.
  2. Du kannst android:windowSoftInputMode="adjustResize" im Eintrag AndroidManifest.xml deiner Aktivität festlegen. Mit dieser Einstellung kann Ihre App die Größe des Software-IME als Einfügungen empfangen, sodass Sie Inhalte entsprechend auffüllen und anordnen können, wenn der IME in Ihrer App angezeigt und ausgeblendet wird.

    <!-- 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">
    

Compose-APIs

Sobald Ihre Activity-Klasse die Kontrolle über die Verarbeitung aller Einfügungen übernimmt, können Sie Compose APIs verwenden, um sicherzustellen, dass Inhalte nicht verdeckt werden und sich interaktive Elemente nicht mit der System-UI überschneiden. Diese APIs synchronisieren auch das Layout Ihrer App mit übernommenen Änderungen.

Dies ist beispielsweise die einfachste Methode, um die Insets auf den Inhalt Ihrer gesamten App anzuwenden:

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

    enableEdgeToEdge()

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

In diesem Snippet werden die safeDrawing-Fenstereinfügungen als Innenrand um den gesamten Inhalt der App angewendet. So wird sichergestellt, dass sich interaktive Elemente nicht mit der System-UI überschneiden. Es bedeutet aber auch, dass keine App hinter der System-UI gezeichnet wird, um einen randlosen Effekt zu erzielen. Damit Sie das gesamte Fenster voll ausschöpfen können, müssen Sie für jeden Bildschirm oder für jede Komponente eine Feinabstimmung vornehmen, in der die Einfügungen angewendet werden.

Alle diese Einsatztypen werden automatisch animiert, wobei IME-Animationen zu API 21 rückportiert werden. Alle Layouts, in denen diese Einfügungen verwendet werden, werden automatisch animiert, wenn sich die eingefügten Werte ändern.

Es gibt zwei Möglichkeiten, diese Einfügungstypen zum Anpassen Ihrer zusammensetzbaren Layouts zu verwenden: Padding-Modifikatoren und Einfügungsgrößenmodifikatoren.

Modifikatoren für das Padding

Modifier.windowInsetsPadding(windowInsets: WindowInsets) wendet die gegebenen Fenstereinfügungen als Innenrand an und verhält sich wie Modifier.padding. Beispielsweise wendet Modifier.windowInsetsPadding(WindowInsets.safeDrawing) die sicheren Zeicheneinsätze als Abstand auf allen vier Seiten an.

Es gibt auch mehrere integrierte Dienstprogrammmethoden für die gängigsten Einsatztypen. Modifier.safeDrawingPadding() ist eine solche Methode und entspricht Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Es gibt analoge Modifikatoren für die anderen eingefügten Typen.

Eingefügte Größenmodifikatoren

Mit den folgenden Modifikatoren wird eine bestimmte Menge an Fenstereinfügungen angewendet. Dazu wird die Größe der Komponente auf die Größe der Einfügungen festgelegt:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Wendet die Anfangsseite von windowInsets auf die Breite an (z. B. Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Wendet die Endeseite von windowInsets auf die Breite an (z. B. Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Wendet die obere Seite von windowInsets als Höhe an (z. B. Modifier.height)

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Wendet die Unterseite von windowInsets auf die Höhe an (z. B. Modifier.height)

Diese Modifikatoren sind besonders nützlich für die Größenanpassung eines Spacer, der den Platz von Einfügungen einnimmt:

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

Verbrauch

Die Modifikatoren für den eingefügten Abstand (windowInsetsPadding und Hilfsfunktionen wie safeDrawingPadding) verbrauchen automatisch den Teil der Einfügungen, die als Innenrand angewendet werden. Wenn wir tiefer in den Kompositionsbaum eintauchen, wissen die verschachtelten Inset-Padding-Modifikatoren und die Einfügungsgrößenmodifikatoren, dass ein Teil der Einfügungen bereits von äußeren eingefügten Padding-Modifikatoren verbraucht wurde, und vermeiden, denselben Teil der Einfügungen mehr als einmal zu verwenden, was zu viel zusätzlichen Platz führen würde.

Mit Modifikatoren für die eingefügte Größe wird außerdem verhindert, dass ein Teil der Einsatze mehrmals verwendet wird, wenn bereits Insets verwendet wurden. Da sie ihre Größe jedoch direkt ändern, verbrauchen sie keine Einsätze selbst.

Daher ändern verschachtelte Padding-Modifikatoren automatisch den Umfang des Paddings, das auf jede zusammensetzbare Funktion angewendet wird.

Im selben LazyColumn-Beispiel wie zuvor wird die Größe von LazyColumn durch den imePadding-Modifikator geändert. In der LazyColumn hat das letzte Element die Größe am unteren Rand der Systemleisten:

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

Wenn der IME geschlossen ist, wendet der imePadding()-Modifikator keinen Innenrand an, da der IME keine Höhe hat. Da mit dem imePadding()-Modifikator kein Padding angewendet wird, werden keine Einfügungen verbraucht und die Höhe von Spacer entspricht der Größe der unteren Seite der Systembalken.

Wenn der IME geöffnet wird, werden die IME-Einsätze entsprechend der Größe des IME animiert und der imePadding()-Modifikator wendet einen Abstand unten an, um die Größe von LazyColumn anzupassen, wenn der IME geöffnet wird. Sobald der imePadding()-Modifikator anfängt, den unteren Innenrand anzuwenden, wird auch diese Anzahl von Einsätzen verbraucht. Daher nimmt die Höhe von Spacer ab, da ein Teil des Abstands für die Systembalken bereits vom imePadding()-Modifikator angewendet wurde. Wenn mit dem imePadding()-Modifikator ein Abstand vom unteren Rand angewendet wird, der größer als die Systembalken ist, ist die Höhe von Spacer null.

Wenn der IME geschlossen wird, erfolgen die Änderungen in umgekehrter Reihenfolge: Der Spacer beginnt, sich von einer Höhe von null aus zu erweitern, sobald imePadding() weniger als die Unterseite der Systemleisten angewendet wird, bis schließlich die Höhe von Spacer mit der Höhe der unteren Seite der Systemleisten übereinstimmt, sobald der IME vollständig animiert ist.

Abbildung 2. Edge-to-Edge Lazy Column mit TextField

Dieses Verhalten erfolgt über die Kommunikation zwischen allen windowInsetsPadding-Modifikatoren und kann auf verschiedene Weise beeinflusst werden.

Modifier.consumeWindowInsets(insets: WindowInsets) verwendet auch Einfügungen auf dieselbe Weise wie Modifier.windowInsetsPadding, wendet die verbrauchten Einfügungen jedoch nicht als Innenrand an. Dies ist in Kombination mit den Einfügungsgrößenmodifikatoren nützlich, um Geschwistern anzuzeigen, dass bereits eine bestimmte Menge von Einfügungen verbraucht 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 einem WindowInsets-Argument, verwendet aber ein beliebiges PaddingValues zur Verarbeitung. Dies ist nützlich, um untergeordnete Elemente zu informieren, wenn der Innenrand oder Abstand über einen anderen Mechanismus als die zusätzlichen Innenrandmodifikatoren bereitgestellt wird, z. B. über eine gewöhnliche Modifier.padding oder Abstandshalter mit fester Höhe:

@OptIn(ExperimentalLayoutApi::class)
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

Wenn die Einfügungen des Rohfensters ohne Verbrauch erforderlich sind, verwenden Sie direkt die WindowInsets-Werte oder verwenden Sie WindowInsets.asPaddingValues(), um eine PaddingValues der Einfügungen zurückzugeben, die vom Verbrauch nicht betroffen sind. Aufgrund der nachfolgenden Vorbehalte sollten Sie jedoch nach Möglichkeit die Padding-Modifikatoren für Fenstereinfügungen und die Größenmodifikatoren für Fenstereinfügungen verwenden.

Phasen der Insets und Jetpack Compose-Phasen

Compose verwendet die zugrunde liegenden AndroidX Core APIs, um Einsätze zu aktualisieren und zu animieren. Diese verwenden die zugrunde liegenden Plattform-APIs zur Verwaltung von Einfügungen. Aufgrund dieses Plattformverhaltens haben Insets eine besondere Beziehung zu den Phasen von Jetpack Composer.

Der Wert von Insets wird nach der Kompositionsphase, aber vor der Layoutphase aktualisiert. Dies bedeutet, dass beim Lesen des Werts von Einfügungen in der Zusammensetzung im Allgemeinen ein Wert der Einfügungen verwendet wird, der einen Frame zu spät kommt. Die auf dieser Seite beschriebenen integrierten Modifikatoren sind so konzipiert, dass sie die Verwendung der Werte der Einfügungen bis zur Layoutphase verzögern. Dadurch wird sichergestellt, dass die eingefügten Werte auf demselben Frame verwendet werden, in dem sie aktualisiert werden.

Tastatur-IME-Animationen mit WindowInsets

Sie können Modifier.imeNestedScroll() auf einen scrollbaren Container anwenden, um den IME automatisch zu öffnen und zu schließen, wenn an das Ende des Containers gescrollt wird.

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 eines UI-Elements, das nach oben und unten scrollt, um Platz für eine Tastatur zu schaffen

Abbildung 1: IME-Animationen

Eingefügte Unterstützung für Material 3-Komponenten

Der Einfachheit halber verarbeiten viele der integrierten Material 3-Zusammensetzungen (androidx.compose.material3) die Einfügungen selbst, je nachdem, wie die zusammensetzbaren Funktionen gemäß den Materialspezifikationen in Ihrer App platziert werden.

Zusammensetzbare Funktionen für die eingefügte Verarbeitung

Nachfolgend finden Sie eine Liste der Materialkomponenten, die Einfügungen automatisch verarbeiten.

App-Leisten

Inhaltscontainer

Gerüst

Standardmäßig stellt Scaffold Einfügungen als Parameter paddingValues bereit, die Sie nutzen und verwenden können. Scaffold wendet die Einfügungen nicht auf Inhalte an. Sie sind dafür verantwortlich. So verwenden Sie beispielsweise diese Einfügungen 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-Einfügungen überschreiben

Sie können den an die zusammensetzbare Funktion übergebenen Parameter windowInsets ändern, um das Verhalten der zusammensetzbaren Funktion zu konfigurieren. Dieser Parameter kann ein anderer Typ von Fenstereinfügung sein, die stattdessen angewendet werden soll, oder er wird durch Übergeben einer leeren Instanz deaktiviert: WindowInsets(0, 0, 0, 0).

Wenn Sie beispielsweise die Einsatzbehandlung für LargeTopAppBar deaktivieren möchten, legen Sie für den Parameter windowInsets eine leere Instanz fest:

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

Interoperabilität mit System-Einbindungen der Ansicht

Möglicherweise müssen Sie die Standardeinfügungen überschreiben, wenn auf Ihrem Bildschirm in derselben Hierarchie Code für Ansichten und Schreiben vorhanden ist. In diesem Fall müssen Sie explizit angeben, in welchem Bereich die Insets aufgenommen und welche ignoriert werden sollen.

Wenn das äußerste Layout beispielsweise ein Android View-Layout ist, sollten Sie die Einfügungen im View-System übernehmen und beim Schreiben ignorieren. Wenn das äußerste Layout eine zusammensetzbare Funktion ist, sollten Sie alternativ die Insets in Compose verwenden und die zusammensetzbaren AndroidView-Elemente entsprechend auffüllen.

Standardmäßig verbraucht jeder ComposeView alle Einfügungen auf der Verbrauchsebene WindowInsetsCompat. Wenn Sie dieses Standardverhalten ändern möchten, setzen Sie ComposeView.consumeWindowInsets auf false.

Weitere Informationen

  • Jetzt für Android: Eine voll funktionsfähige Android-App, die vollständig mit Kotlin und Jetpack Compose entwickelt wurde.