Fenstereinsätze in WebView

WebView verwaltet die Inhaltsausrichtung mithilfe von zwei Darstellungsbereichen: dem Layout-Darstellungsbereich (Seitengröße) und dem visuellen Darstellungsbereich (der Teil der Seite, den der Nutzer tatsächlich sieht). Der Layout-Darstellungsbereich ist im Allgemeinen statisch, der visuelle Darstellungsbereich ändert sich jedoch dynamisch, wenn Nutzer zoomen, scrollen oder wenn System-UI-Elemente (z. B. die Bildschirmtastatur) angezeigt werden.

Funktionskompatibilität

Die WebView-Unterstützung für Fenster-Insets hat sich im Laufe der Zeit weiterentwickelt, um das Verhalten von Webinhalten an die Erwartungen nativer Android-Apps anzupassen:

Meilenstein Funktion hinzugefügt Umfang
M136 displayCutout() und systemBars() werden über CSS-Safe-Area-Insets unterstützt. Nur Vollbild-WebViews.
M139 Unterstützung für ime() (Input Method Editor, eine Tastatur) durch Größenänderung des visuellen Darstellungsbereichs. Alle WebViews.
M144 Unterstützung für displayCutout() und systemBars() Alle WebViews (unabhängig vom Vollbildstatus).

Weitere Informationen finden Sie unter WindowInsetsCompat.

Kernmechanik

WebView verarbeitet Insets über zwei Hauptmechanismen:

  • Sichere Bereiche (displayCutout, systemBars): WebView leitet diese Abmessungen über die CSS-Variablen „safe-area-inset-*“ an Webinhalte weiter. So können Entwickler verhindern, dass ihre eigenen interaktiven Elemente (z. B. Navigationsleisten) durch Notches oder Statusleisten verdeckt werden.

  • Größenänderung des visuellen Darstellungsbereichs mit dem Eingabemethoden-Editor (IME): Ab M139 ändert der Eingabemethoden-Editor (IME) die Größe des visuellen Darstellungsbereichs direkt. Dieser Mechanismus zur Größenänderung basiert ebenfalls auf der WebView-Fenster-Schnittmenge. Wenn beispielsweise im Android-Multitasking-Modus der untere Teil einer WebView 200 dp unter den unteren Rand des Fensters reicht, ist der visuelle Darstellungsbereich 200 dp kleiner als die Größe der WebView. Diese Größenänderung des visuellen Darstellungsbereichs (sowohl für die IME- als auch für die WebView-Fensterschnittmenge) wird nur auf den unteren Bereich der WebView angewendet. Dieser Mechanismus unterstützt keine Größenänderung für Überlappungen links, rechts oder oben. Das bedeutet, dass angedockte Tastaturen, die an diesen Rändern angezeigt werden, keine Größenänderung des visuellen Darstellungsbereichs auslösen.

Zuvor blieb der visuelle Darstellungsbereich unverändert, sodass Eingabefelder oft hinter der Tastatur verborgen waren. Wenn die Größe des Darstellungsbereichs geändert wird, wird der sichtbare Teil der Seite standardmäßig scrollbar, sodass Nutzer verdeckte Inhalte erreichen können.

Logik für Grenzen und Überschneidungen

WebView sollte nur dann von null verschiedene Werte für die Einrückung erhalten, wenn System-UI-Elemente (Leisten, Display-Aussparungen oder die Tastatur) direkt mit den Bildschirmgrenzen von WebView überlappen. Wenn sich eine WebView nicht mit diesen UI-Elementen überschneidet (z. B. wenn eine WebView auf dem Bildschirm zentriert ist und die Systemleisten nicht berührt), sollten diese Einzüge als null empfangen werden.

Wenn Sie diese Standardlogik überschreiben und dem Webinhalt die vollständigen Systemabmessungen unabhängig von einer Überlappung zur Verfügung stellen möchten, verwenden Sie die Methode setOnApplyWindowInsetsListener und geben Sie das ursprüngliche, unveränderte windowInsets-Objekt vom Listener zurück. Durch die Bereitstellung vollständiger Systemabmessungen kann die Designkonsistenz gewährleistet werden, da Webinhalte unabhängig von der aktuellen Position der WebView an der Gerätehardware ausgerichtet werden können. So wird ein reibungsloser Übergang gewährleistet, wenn die WebView verschoben oder maximiert wird, um die Bildschirmränder zu berühren.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(myWebView) { _, windowInsets ->
    // By returning the original windowInsets object, we override the default
    // behavior that zeroes out system insets (like system bars or display
    // cutouts) when they don't directly overlap the WebView's screen bounds.
    windowInsets
}

Java

ViewCompat.setOnApplyWindowInsetsListener(myWebView, (v, windowInsets) -> {
    // By returning the original windowInsets object, we override the default
    // behavior that zeroes out system insets (like system bars or display
    // cutouts) when they don't directly overlap the WebView's screen bounds.
    return windowInsets;
});

Ereignisse zur Größenänderung verwalten

Da die Sichtbarkeit der Tastatur jetzt eine Größenänderung des visuellen Darstellungsbereichs auslöst, kann es im Webcode häufiger zu Größenänderungsereignissen kommen. Entwickler müssen dafür sorgen, dass ihr Code nicht auf diese Ereignisse zur Größenänderung reagiert, indem der Elementfokus gelöscht wird. Dadurch entsteht eine Schleife aus Fokusverlust und Schließen der Tastatur, die Nutzereingaben verhindert:

  1. Der Nutzer fokussiert ein Eingabeelement.
  2. Die Tastatur wird eingeblendet und löst ein Ereignis zur Größenänderung aus.
  3. Der Code der Website löscht den Fokus als Reaktion auf die Größenänderung.
  4. Die Tastatur wird ausgeblendet, weil der Fokus verloren gegangen ist.

Um dieses Verhalten zu vermeiden, sollten Sie die Listener auf der Website prüfen, um sicherzustellen, dass Änderungen am Darstellungsbereich nicht unbeabsichtigt die JavaScript-Funktion blur() oder das Verhalten zum Löschen des Fokus auslösen.

Inset-Verarbeitung implementieren

Die Standardeinstellungen von WebView funktionieren bei den meisten Apps automatisch. Wenn Ihre App jedoch benutzerdefinierte Layouts verwendet (z. B. wenn Sie eigene Abstände hinzufügen, um die Statusleiste oder die Tastatur zu berücksichtigen), können Sie die folgenden Ansätze verwenden, um die Zusammenarbeit von Webinhalten und nativer Benutzeroberfläche zu verbessern. Wenn Ihre native UI ein Padding auf einen Container basierend auf WindowInsets anwendet, müssen Sie diese Insets korrekt verwalten, bevor sie die WebView erreichen, um ein doppeltes Padding zu vermeiden.

Doppeltes Padding tritt auf, wenn das native Layout und der Webinhalt dieselben Abmessungen für den Abstand verwenden, was zu redundanten Abständen führt. Stellen Sie sich beispielsweise ein Smartphone mit einer 40 px hohen Statusleiste vor. Sowohl die native Ansicht als auch die WebView sehen den 40-Pixel-Inset. Beide fügen 40 Pixel Padding hinzu, sodass der Nutzer oben eine Lücke von 80 Pixeln sieht.

Der Zeroing-Ansatz

Um ein doppeltes Padding zu verhindern, müssen Sie dafür sorgen, dass nach der Verwendung einer Inset-Dimension für das Padding in einer nativen Ansicht diese Dimension mit Insets.NONE für ein neues WindowInsets-Objekt auf null zurückgesetzt wird, bevor das geänderte Objekt in der Ansichtshierarchie an die WebView weitergegeben wird.

Wenn Sie Padding auf eine übergeordnete Ansicht anwenden, sollten Sie im Allgemeinen den Nullstellungsansatz verwenden und Insets.NONE anstelle von WindowInsetsCompat.CONSUMED festlegen. Die Rückgabe von WindowInsetsCompat.CONSUMED kann in bestimmten Situationen funktionieren. Es kann jedoch zu Problemen kommen, wenn der Handler Ihrer App die Insets ändert oder ein eigenes Padding hinzufügt. Der Zeroing-Ansatz hat diese Einschränkungen nicht.

Ghost-Padding durch Nullsetzen von Insets vermeiden

Wenn Sie die Insets nutzen, nachdem die App zuvor nicht genutzte Insets übergeben hat, oder wenn sich die Insets ändern (z. B. wenn die Tastatur ausgeblendet wird), wird die WebView durch die Nutzung nicht über die erforderliche Aktualisierung benachrichtigt. Dies kann dazu führen, dass die WebView ein Ghost-Padding aus einem vorherigen Zustand beibehält (z. B. das Tastatur-Padding, nachdem die Tastatur ausgeblendet wurde).

Das folgende Beispiel zeigt eine fehlerhafte Interaktion zwischen der App und WebView:

  1. Anfangszustand:Die App übergibt anfangs nicht verbrauchte Insets (z. B. displayCutout() oder systemBars()) an die WebView, die intern Padding auf den Webinhalt anwendet.
  2. Statusänderung und Fehler:Wenn sich der Status der App ändert (z. B. die Tastatur wird ausgeblendet) und die App die resultierenden Insets durch Rückgabe von WindowInsetsCompat.CONSUMED verarbeitet.
  3. Benachrichtigung blockiert:Wenn die Insets verarbeitet werden, kann das Android-System die erforderliche Aktualisierungsbenachrichtigung nicht über die Ansichtshierarchie an die WebView senden.
  4. Ghost-Padding:Da die WebView das Update nicht erhält, behält sie das Padding aus dem vorherigen Zustand bei. Dadurch entsteht Ghost-Padding (z. B. bleibt das Tastatur-Padding erhalten, nachdem die Tastatur ausgeblendet wurde).

Verwenden Sie stattdessen WindowInsetsCompat.Builder, um die verarbeiteten Typen auf null zu setzen, bevor Sie das Objekt an die untergeordneten Ansichten übergeben. Dadurch wird die WebView darüber informiert, dass diese spezifischen Insets bereits berücksichtigt wurden, während die Benachrichtigung in der Ansichtshierarchie weitergegeben wird.

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, windowInsets ->
    // 1. Identify the inset types you want to handle natively
    val types = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()

    // 2. Extract the dimensions and apply them as padding to the native container
    val insets = windowInsets.getInsets(types)
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom)

    // 3. Return a new WindowInsets object with the handled types set to NONE (zeroed).
    // This informs the WebView that these areas are already padded, preventing
    // double-padding while still allowing the WebView to update its internal state.
    WindowInsetsCompat.Builder(windowInsets)
        .setInsets(types, Insets.NONE)
        .build()
}

Java

ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> {
    // 1. Identify the inset types you want to handle natively
    int types = WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout();

    // 2. Extract the dimensions and apply them as padding to the native container
    Insets insets = windowInsets.getInsets(types);
    rootView.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // 3. Return a new Insets object with the handled types set to NONE (zeroed).
    // This informs the WebView that these areas are already padded, preventing
    // double-padding while still allowing the WebView to update its internal
    // state.
    return new WindowInsetsCompat.Builder(windowInsets)
            .setInsets(types, Insets.NONE)
            .build();
});

Deaktivieren

So deaktivieren Sie diese modernen Verhaltensweisen und kehren zur alten Darstellungsbereichsverarbeitung zurück:

  1. Insets abfangen: Verwenden Sie setOnApplyWindowInsetsListener oder überschreiben Sie onApplyWindowInsets in einer WebView-Unterklasse.

  2. Insets löschen:Gibt einen verbrauchten Satz von Insets (z. B. WindowInsetsCompat.CONSUMED) von Anfang an zurück. Dadurch wird verhindert, dass die Inset-Benachrichtigung überhaupt an die WebView weitergegeben wird. Die moderne Größenanpassung des Darstellungsbereichs wird deaktiviert und die WebView behält die ursprüngliche Größe des visuellen Darstellungsbereichs bei.