Fenstereinfügungen in Compose

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

Sich von den Rand zu Rand bewegen, um hinter den Systemleisten zu zeichnen
Abbildung 1: Durch das Zeichnen von Rand zu Rand hinter die Systemleisten

Standardmäßig ist die Benutzeroberfläche Ihrer Anwendung auf die System-UI wie die Statusleiste und die Navigationsleiste beschränkt. Dadurch wird sichergestellt, dass die Inhalte deiner App nicht von System-UI-Elementen verdeckt werden.

Wir empfehlen jedoch, Apps für die Anzeige in Bereichen zu aktivieren, in denen auch die System-UI angezeigt wird. Dadurch wird die Nutzererfahrung verbessert und deine App kann den verfügbaren Fensterbereich optimal nutzen. Auf diese Weise können Apps auch gemeinsam mit der System-UI animiert werden, insbesondere wenn die Softwaretastatur ein- und ausgeblendet wird.

Das Aktivieren der Anzeige in diesen Regionen und das Anzeigen von Inhalten hinter der System-UI wird als Edge-to-Edge-Angriff bezeichnet. Auf dieser Seite erfahren Sie mehr über die verschiedenen Arten von Einfügungen, wie Sie Edge-to-Edge-Funktionen aktivieren und wie Sie mit den eingefügten APIs Ihre Benutzeroberfläche animieren und vermeiden, dass Teile Ihrer App verdeckt werden.

Grundlagen

Bei Apps, die auf den neuesten Stand gebracht werden, müssen wichtige Inhalte und Interaktionen nicht von der System-UI verdeckt werden. Wenn sich beispielsweise eine Schaltfläche hinter der Navigationsleiste befindet, können Nutzer möglicherweise nicht darauf klicken.

Die Größe der System-UI und Informationen dazu, wo sie platziert wird, werden über Einsätze angegeben.

Jeder Teil der System-UI hat eine entsprechende Einfügungsart, die seine Größe und seine Platzierung beschreibt. Beispielsweise geben die Einfügungen für die Statusleiste die Größe und Position der Statusleiste an, während die Einfügungen für die Navigationsleiste die Größe und Position der Navigationsleiste angeben. Jede Art von Einfügung besteht aus vier Pixelmaßen: oben, links, rechts und unten. Diese Dimensionen geben an, wie weit die System-UI von den entsprechenden Seiten des App-Fensters entfernt ist. Damit eine Überlappung mit dieser Art von System-UI vermieden wird, muss die App-UI um diesen Wert eingegrenzt werden.

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

WindowInsets.statusBars

Die Einsätze, die die Statusleisten beschreiben. Dies sind die oberen Leisten der System-UI mit Benachrichtigungssymbolen und anderen Anzeigen.

WindowInsets.statusBarsIgnoringVisibility

Die Statusleistenelemente, die angeben, wann sie sichtbar sind. Wenn die Statusleisten derzeit ausgeblendet sind (aufgrund des Wechsels des immersiven Vollbildmodus), sind die Einsätze der Hauptstatusleiste leer, die Einfügungen jedoch nicht.

WindowInsets.navigationBars

Die Einsätze, die die Navigationsleisten beschreiben. 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, die angibt, wann sie sichtbar sind. Wenn die Navigationsleisten derzeit ausgeblendet sind (aufgrund des Wechsels des immersiven Vollbildmodus), sind die Einfügungen der Hauptnavigationsleiste leer, aber diese Einfügungen sind nicht leer.

WindowInsets.captionBar

Der Einsatz, der die Fenstergestaltung der System-UI beschreibt, wenn es sich um ein Freiformfenster handelt, z. B. die obere Titelleiste.

WindowInsets.captionBarIgnoringVisibility

Die Untertitelleiste zeigt an, wann sie sichtbar sind. Wenn die Untertitelleisten derzeit ausgeblendet sind, sind die Haupteinsätze der Untertitelleiste leer, aber diese Einsätze sind nicht leer.

WindowInsets.systemBars

Die Kombination der Systemleistenelemente, einschließlich der Statusleisten, der Navigationsleisten und der Untertitelleiste.

WindowInsets.systemBarsIgnoringVisibility

Die Systemleisten für den Zeitpunkt ihrer Sichtbarkeit. Wenn die Systemleisten derzeit ausgeblendet sind (aufgrund des Wechsels in den immersiven Vollbildmodus), sind die Einsätze der Hauptsystemleiste leer, aber diese Einfügungen sind nicht leer.

WindowInsets.ime

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

WindowInsets.imeAnimationSource

Einsätze, die angeben, wie viel Platz die Softwaretastatur vor der aktuellen Tastaturanimation eingenommen hat.

WindowInsets.imeAnimationTarget

Einsätze, die angeben, wie viel Platz die Softwaretastatur nach der aktuellen Tastaturanimation einnimmt.

WindowInsets.tappableElement

Eine Art von Einfügungen, die detailliertere Informationen über die Navigations-UI beschreiben. Sie geben an, wie viel Platz vom System und nicht von der App genutzt wird. Bei transparenten Navigationsleisten mit Gestennavigation können einige App-Elemente über die Benutzeroberfläche der Systemnavigation angetippt werden.

WindowInsets.tappableElementIgnoringVisibility

Die Einfügungen für antippbare Elemente, wenn sie sichtbar sind. Wenn die antippbaren Elemente derzeit verborgen sind (aufgrund des Wechsels im immersiven Vollbildmodus), sind die Einsätze der antippbaren Elemente leer, die Einsätze sind jedoch nicht leer.

WindowInsets.systemGestures

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

WindowInsets.mandatorySystemGestures

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

WindowInsets.displayCutout

Die Einfügungen, die den erforderlichen Abstand zur Vermeidung einer Überlappung mit einer Display-Aussparung (Einkerbung oder Nadelloch) darstellen.

WindowInsets.waterfall

Die Einsätze, die die gebogenen Bereiche einer Wasserfall-Anzeige darstellen. Eine Wasserfall-Anzeige hat gekrümmte Bereiche entlang der Bildschirmränder, in denen sich der Bildschirm an die Seiten des Geräts wickelt.

Diese Typen werden in drei „sicheren“ Einsätzen zusammengefasst, die dafür sorgen, dass Inhalte nicht verdeckt werden:

Diese „sicheren“ Einsätze schützen Inhalte auf unterschiedliche Weise, je nach den zugrunde liegenden Plattform-Einsätzen:

  • Verwenden Sie WindowInsets.safeDrawing, um Inhalte zu schützen, die nicht unter einer System-UI dargestellt werden sollen. Dies ist die häufigste Verwendung von Einsätzen, um zu verhindern, dass Inhalte gezeichnet werden, die von der System-UI teilweise oder vollständig verdeckt werden.
  • Verwenden Sie WindowInsets.safeGestures, um Inhalte mit Touch-Gesten zu schützen. Dadurch wird verhindert, dass Systemgesten mit App-Gesten in Konflikt stehen, z. B. für Tabellenblätter am unteren Rand, Karussells oder in Spielen.
  • Verwende WindowInsets.safeContent in Kombination aus WindowInsets.safeDrawing und WindowInsets.safeGestures, damit es keine Überschneidungen beim Inhalt gibt.

Einrichtung von Einsätzen

Mit den folgenden Einrichtungsschritten können Sie Ihrer App die vollständige Kontrolle darüber geben, wo Inhalte abgerufen werden. Ohne diese Schritte zeichnet Ihre App möglicherweise schwarze oder einfarbige Farben hinter der System-UI oder wird nicht synchron mit der Softwaretastatur animiert.

  1. Rufen Sie enableEdgeToEdge() in Activity.onCreate auf. Bei diesem Aufruf wird Ihre App hinter der System-UI angezeigt. Ihre App steuert dann, wie diese Einfügungen zum Anpassen der UI verwendet werden.
  2. Lege android:windowSoftInputMode="adjustResize" im Eintrag AndroidManifest.xml deiner Aktivität fest. Mit dieser Einstellung kann Ihre App die Größe des Software-IME als Einsätze empfangen, mit denen Sie Inhalte entsprechend auffüllen und anordnen können, wenn der IME in Ihrer App erscheint und wieder 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 Activity die Kontrolle über die Verarbeitung aller Einsätze übernommen hat, können Sie mit Compose APIs dafür sorgen, dass Inhalte nicht verdeckt werden und sich keine interaktiven Elemente mit der System-UI überschneiden. Diese APIs synchronisieren auch das Layout Ihrer Anwendung mit Einfügungsänderungen.

Dies ist beispielsweise die einfachste Methode, um die Einfügungen auf den Inhalt Ihrer gesamten Anwendung anzuwenden:

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

    enableEdgeToEdge()

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

Dieses Snippet wendet die safeDrawing-Fenstereinfügungen als Innenrand um den gesamten Inhalt der App an. Dadurch wird sichergestellt, dass sich die interaktiven Elemente nicht mit der System-UI überschneiden, bedeutet dies aber auch, dass die App nicht hinter die System-UI zieht, um einen randvollen Effekt zu erzielen. Damit Sie das gesamte Fenster optimal nutzen können, müssen Sie für jeden Bildschirm oder für die einzelnen Komponenten genau abstimmen, wo die Einfügungen angewendet werden.

Alle diese Einfügungstypen werden automatisch mit IME-Animationen animiert, die an API 21 zurückgesendet werden. Alle Layouts, die diese Werte enthalten, werden ebenfalls automatisch animiert, wenn sich die Werte für den Einsatz ändern.

Es gibt im Wesentlichen zwei Möglichkeiten, diese Einfügungstypen zum Anpassen Ihrer zusammensetzbaren Layouts zu verwenden: Modifikatoren für den Abstand und Einfügungsgrößenmodifikatoren.

Padding-Modifikatoren

Modifier.windowInsetsPadding(windowInsets: WindowInsets) wendet die angegebenen Fenstereinfügungen als Innenrand an, genau wie Modifier.padding. Modifier.windowInsetsPadding(WindowInsets.safeDrawing) wendet beispielsweise die sicheren Zeicheneinsätze als Abstand auf allen vier Seiten an.

Es gibt auch mehrere integrierte Dienstprogrammmethoden für die gängigsten Einfügungstypen. Modifier.safeDrawingPadding() ist eine solche Methode und entspricht Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Für die anderen Einfügungstypen gibt es entsprechende Modifikatoren.

Eingesetzte Größenmodifikatoren

Mit den folgenden Modifikatoren wird eine bestimmte Anzahl von Fenstereinfügungen angewendet, indem die Größe der Komponente auf die Größe der Einfügungen festgelegt wird:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Wendet die Startseite von windowInsets als Breite an (z. B. Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Wendet die Endseite von windowInsets als 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 als Höhe an (z. B. Modifier.height)

Diese Modifikatoren sind besonders nützlich, um die Größe eines Spacer-Elements anzupassen, das den Platz der Einfügungen einnimmt:

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

Eingefügter Verbrauch

Die eingefügten Padding-Modifikatoren (windowInsetsPadding und Hilfsprogramme wie safeDrawingPadding) nutzen automatisch den Teil der Einfügungen, der als Auffüllung angewendet wird. Verschachtelte Padding-Modifikatoren und Modifikatoren für die Größe von eingefügten Texten erkennen, dass ein Teil der Einfügungen bereits von äußeren Einfügungs-Modifikatoren konsumiert wurden. Bei genauerer Betrachtung des Kompositionsbaums wird vermieden, denselben Teil der Einsätze mehr als einmal zu verwenden, was zu viel zusätzlichen Platz bedeuten würde.

Eingefügte Größenmodifikatoren vermeiden außerdem, denselben Teil von Einsätzen mehr als einmal zu verwenden, wenn Einsätze bereits verarbeitet wurden. Da sie jedoch ihre Größe direkt ändern, verbrauchen sie keine Einsätze selbst.

Das führt dazu, dass bei verschachtelten Padding-Modifikatoren automatisch die auf jede zusammensetzbare Funktion angewendete Auffüllung geändert wird.

Im selben LazyColumn-Beispiel wie zuvor wird die Größe von LazyColumn durch den imePadding-Modifikator geändert. Innerhalb von LazyColumn hat das letzte Element die Höhe des unteren Rands der Systemleisten:

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

Wenn der IME geschlossen ist, wendet der Modifikator imePadding() keinen Innenrand an, da er keine Höhe hat. Da der imePadding()-Modifikator keine Auffüllung anwendet, werden keine Einsätze verwendet. Die Höhe von Spacer entspricht dann der Größe der unteren Seite der Systembalken.

Wenn der IME geöffnet wird, werden die IME-Einfügungen an die Größe des IME angepasst. Der imePadding()-Modifikator beginnt mit dem unteren Innenrand, um die Größe des LazyColumn beim Öffnen des IMEs anzupassen. Wenn der imePadding()-Modifikator mit dem Anwenden des unteren Abstands beginnt, verbraucht er auch diese Anzahl von Einsätzen. Daher nimmt die Höhe von Spacer ab, da ein Teil des Abstands für die Systembalken bereits durch den imePadding()-Modifikator angewendet wurde. Wenn der imePadding()-Modifikator einen unteren Abstand einfügt, der größer als die Systemleisten ist, ist die Höhe des Spacer gleich null.

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

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

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

Modifier.consumeWindowInsets(insets: WindowInsets) verarbeitet ebenfalls Einsätze auf dieselbe Weise wie Modifier.windowInsetsPadding, wendet jedoch die aufgenommenen Einsätze nicht als Auffüllung an. Dies ist in Kombination mit den Größenmodifikatoren nützlich, um gleichgeordnete Elemente darüber zu informieren, dass eine bestimmte Anzahl von Einsätzen bereits 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 ähnlich wie die Version mit einem WindowInsets-Argument, aber zum Verarbeiten ist ein beliebiges PaddingValues erforderlich. Dies ist nützlich, um Kinder zu informieren, wenn die Auffüllung oder der Abstand von einem anderen Mechanismus als den eingefügten Padding-Modifikatoren bereitgestellt wird, z. B. von einem gewöhnlichen 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 Einsätze der unbearbeiteten Fenster ohne Verbrauch benötigt werden, verwenden Sie die WindowInsets-Werte direkt oder WindowInsets.asPaddingValues(), um einen PaddingValues der Einsätze zurückzugeben, die vom Verbrauch nicht betroffen sind. Aufgrund der folgenden Vorbehalte sollten Sie jedoch nach Möglichkeit immer die Padding-Modifizierer für Fenstereinsätze und die Größenmodifikatoren für Fenstereinfügungen verwenden.

Einsätze und Jetpack Compose-Phasen

Compose verwendet die zugrunde liegenden AndroidX Core APIs zum Aktualisieren und Animieren von Einsätzen, die die zugrunde liegenden Plattform-APIs zur Verwaltung von Einsätzen verwenden. Aufgrund dieses Plattformverhaltens haben Einsätze eine besondere Beziehung zu den Phasen von Jetpack Compose.

Die Werte der Einsätze werden nach der Zusammensetzungsphase, jedoch vor der Layoutphase aktualisiert. Das bedeutet, dass beim Lesen des Werts von Einfügungen in der Zusammensetzung im Allgemeinen ein Wert der Einsätze verwendet wird, der einen Frame zu spät ist. Die auf dieser Seite beschriebenen integrierten Modifikatoren werden so konzipiert, dass die Werte der Einfügungen bis zur Layoutphase verzögert verwendet werden. Dadurch wird sichergestellt, dass die eingefügten Werte im selben 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 zum 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

Unterstützung für Material 3-Komponenten

Der Einfachheit halber werden bei vielen der integrierten „Material 3 Composables“ (androidx.compose.material3) Einsätze verwendet, je nachdem, wie die zusammensetzbaren Funktionen gemäß den Material-Spezifikationen in Ihrer App platziert werden.

Einfügung für zusammensetzbare Funktionen

Nachfolgend finden Sie eine Liste der Materialkomponenten, die Einsätze automatisch verarbeiten.

App-Leisten

Inhaltscontainer

Gerüst

Standardmäßig stellt Scaffold Werte als Parameter paddingValues zur Verfügung, die Sie verwenden und verwenden können. Scaffold wendet die Einsätze nicht auf Inhalte an. Sie sind dafür verantwortlich. So können Sie beispielsweise diese Einfügungen mit einem LazyColumn in einem Scaffold verwenden:

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])
            )
        }
    }
}

Standardeinsätze überschreiben

Sie können das Verhalten der zusammensetzbaren Funktion konfigurieren, indem Sie den an die zusammensetzbaren Funktion übergebenen Parameter windowInsets ändern. Dieser Parameter kann eine andere Art von Fenstereinsatz sein, der stattdessen angewendet werden soll, oder er wird deaktiviert, indem eine leere Instanz übergeben wird: WindowInsets(0, 0, 0, 0).

Wenn Sie beispielsweise die Verarbeitung von Platzhaltern 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 Systemeinsätzen „View“

Möglicherweise müssen Sie Standardeinfügungen überschreiben, wenn sich auf Ihrem Bildschirm sowohl Ansichten als auch Code in einer Hierarchie befinden. In diesem Fall müssen Sie genau angeben, in welcher Version die Einfügungen aufgenommen und in welchen ignoriert werden sollen.

Wenn Ihr äußerstes Layout beispielsweise ein Android View-Layout ist, sollten Sie die Einfügungen im View-System verwenden und für Compose ignorieren. Wenn Ihr äußerstes Layout eine zusammensetzbare Funktion ist, sollten Sie die Einfügungen in Compose verwenden und die AndroidView-zusammensetzbaren Funktionen entsprechend auffüllen.

Standardmäßig nutzt jeder ComposeView alle Einsätze auf der Verbrauchsebene WindowInsetsCompat. Wenn Sie dieses Standardverhalten ändern möchten, setzen Sie ComposeView.consumeWindowInsets auf false.

Ressourcen

  • Jetzt in Android: Eine voll funktionsfähige Android-App, die vollständig mit Kotlin und Jetpack Compose erstellt wurde.