Zrozumienie wcięć w oknie w WebView

WebView zarządza wyrównaniem treści za pomocą 2 widocznych obszarów: widocznego obszaru układu (rozmiaru strony) i widocznego obszaru wizualnego (części strony, którą użytkownik faktycznie widzi). Widoczny obszar w układzie jest zwykle statyczny, ale widoczny obszar zmienia się dynamicznie, gdy użytkownicy powiększają lub przewijają widok albo gdy pojawiają się elementy interfejsu systemu (np. klawiatura ekranowa).

Zgodność funkcji

Obsługa wstawek okien w WebView zmieniała się z czasem, aby dostosować zachowanie treści internetowych do oczekiwań dotyczących natywnych aplikacji na Androida:

Kamień milowy Dodano funkcję Zakres
M136 displayCutout() i systemBars() za pomocą CSS safe-area-insets. Tylko widoki WebView na pełnym ekranie.
M139 ime() (edytor metody wprowadzania, czyli klawiatura) dzięki zmianie rozmiaru widocznego obszaru. Wszystkie komponenty WebView.
M144 displayCutout()systemBars(). Wszystkie widoki WebView (niezależnie od stanu pełnego ekranu).

Więcej informacji znajdziesz w sekcji WindowInsetsCompat.

Podstawowe mechanizmy

WebView obsługuje wstawki za pomocą 2 głównych mechanizmów:

  • Obszary bezpieczne (displayCutout, systemBars): WebView przekazuje te wymiary do treści internetowych za pomocą zmiennych CSS safe-area-inset-*. Dzięki temu deweloperzy mogą zapobiegać zasłanianiu własnych elementów interaktywnych (takich jak paski nawigacyjne) przez wycięcia lub paski stanu.

  • Zmiana rozmiaru widocznego obszaru za pomocą edytora metody wprowadzania (IME): od wersji M139 edytor metody wprowadzania (IME) bezpośrednio zmienia rozmiar widocznego obszaru. Ten mechanizm zmiany rozmiaru również opiera się na przecięciu widoku WebView z oknem. Na przykład w trybie wielozadaniowości na Androidzie, jeśli dolna krawędź komponentu WebView wystaje 200 dp poniżej dolnej krawędzi okna, widoczny obszar wyświetlania jest o 200 dp mniejszy niż rozmiar komponentu WebView. Zmiana rozmiaru widocznego obszaru (zarówno w przypadku klawiatury IME, jak i przecięcia okna WebView) jest stosowana tylko u dołu komponentu WebView. Ten mechanizm nie obsługuje zmiany rozmiaru w przypadku nakładania się elementów po lewej, prawej lub górnej stronie. Oznacza to, że klawiatury dokowane pojawiające się na tych krawędziach nie powodują zmiany rozmiaru widocznego obszaru.

Wcześniej widoczny obszar pozostawał stały, często zasłaniając pola wprowadzania za klawiaturą. Po zmianie rozmiaru widocznego obszaru widoczna część strony domyślnie staje się przewijalna, dzięki czemu użytkownicy mogą dotrzeć do zasłoniętych treści.

Logika granic i nakładania się

Wartości wstawki różne od zera powinny być przekazywane do WebView tylko wtedy, gdy elementy interfejsu systemu (paski, wycięcia na wyświetlaczu lub klawiatura) bezpośrednio nakładają się na granice ekranu WebView. Jeśli komponent WebView nie nakłada się na te elementy interfejsu (np. jest wyśrodkowany na ekranie i nie dotyka pasków systemowych), powinien otrzymywać te wstawki jako zero.

Aby zastąpić tę domyślną logikę i przekazać treściom internetowym pełne wymiary systemu niezależnie od nakładania się, użyj metody setOnApplyWindowInsetsListener i zwróć oryginalny, niezmodyfikowany obiekt windowInsets z odbiornika. Podanie pełnych wymiarów systemu może pomóc w zapewnieniu spójności projektu, ponieważ umożliwia dopasowanie treści internetowych do sprzętu urządzenia niezależnie od bieżącej pozycji komponentu WebView. Zapewnia to płynne przejście, gdy komponent WebView przesuwa się lub rozszerza, aby dotknąć krawędzi ekranu.

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;
});
.

Zarządzanie zdarzeniami zmiany rozmiaru

Widoczność klawiatury powoduje teraz zmianę rozmiaru widocznego obszaru, więc kod internetowy może częściej rejestrować zdarzenia zmiany rozmiaru. Deweloperzy muszą zadbać o to, aby ich kod nie reagował na te zdarzenia zmiany rozmiaru przez usuwanie zaznaczenia elementu. Powoduje to utratę zaznaczenia i zamknięcie klawiatury, co uniemożliwia wprowadzanie danych przez użytkownika:

  1. Użytkownik skupia się na elemencie wejściowym.
  2. Wyświetli się klawiatura, co spowoduje zdarzenie zmiany rozmiaru.
  3. Kod witryny usuwa zaznaczenie w odpowiedzi na zmianę rozmiaru.
  4. Klawiatura jest ukrywana, ponieważ utracono fokus.

Aby temu zapobiec, sprawdź po stronie internetowej odbiorniki, aby upewnić się, że zmiany w obszarze wyświetlania nie wywołują przypadkowo funkcji JavaScriptu blur() ani nie powodują wyczyszczenia fokusu.

Implementowanie obsługi wstawień

Domyślne ustawienia WebView działają automatycznie w przypadku większości aplikacji. Jeśli jednak aplikacja korzysta z niestandardowych układów (np. dodajesz własny margines, aby uwzględnić pasek stanu lub klawiaturę), możesz zastosować te metody, aby poprawić współpracę treści internetowych i natywnego interfejsu. Jeśli interfejs natywny stosuje do kontenera dopełnienie na podstawie wartości WindowInsets, musisz prawidłowo zarządzać tymi wstawkami, zanim dotrą one do komponentu WebView, aby uniknąć podwójnego dopełnienia.

Podwójne dopełnienie to sytuacja, w której układ natywny i treści internetowe stosują te same wymiary wstawki, co powoduje nadmiarowe odstępy. Załóżmy na przykład, że telefon ma pasek stanu o wysokości 40 pikseli. Zarówno widok natywny, jak i WebView widzą wcięcie o wartości 40 pikseli. Oba dodają 40 pikseli dopełnienia, co powoduje, że użytkownik widzi u góry odstęp o wielkości 80 pikseli.

Podejście zerowania

Aby zapobiec podwójnemu dopełnieniu, musisz zadbać o to, aby po tym, jak widok natywny użyje wymiaru wstawki do dopełnienia, zresetować ten wymiar do zera za pomocą Insets.NONE w nowym obiekcie WindowInsets przed przekazaniem zmodyfikowanego obiektu w dół hierarchii widoków do elementu WebView.

Podczas stosowania do widoku nadrzędnego dopełnienia zwykle należy używać metody zerowania, ustawiając Insets.NONE zamiast WindowInsetsCompat.CONSUMED. W niektórych sytuacjach może działać zwracanie WindowInsetsCompat.CONSUMED. Może jednak napotkać problemy, jeśli program obsługi aplikacji zmieni marginesy lub doda własne dopełnienie. Podejście polegające na wyzerowaniu nie ma tych ograniczeń.

Unikanie pustych marginesów przez wyzerowanie wstawień

Jeśli wykorzystasz wstawki, gdy aplikacja wcześniej przekazała niewykorzystane wstawki, lub jeśli wstawki ulegną zmianie (np. klawiatura zostanie ukryta), wykorzystanie ich uniemożliwi WebView otrzymanie niezbędnego powiadomienia o aktualizacji. Może to spowodować, że element WebView zachowa dopełnienie widmo z poprzedniego stanu (np. zachowa dopełnienie klawiatury po jej ukryciu).

Poniższy przykład pokazuje nieprawidłową interakcję między aplikacją a widokiem WebView:

  1. Stan początkowy: aplikacja początkowo przekazuje do komponentu WebView nieużyte wstawki (np.displayCutout() lub systemBars()), który wewnętrznie stosuje do treści internetowych dopełnienie.
  2. Zmiana stanu i błąd: jeśli aplikacja zmienia stan (np. klawiatura się chowa) i decyduje się obsłużyć wynikające z tego wstawki, zwracając wartość WindowInsetsCompat.CONSUMED.
  3. Powiadomienie zablokowane: wykorzystanie wstawek uniemożliwia systemowi Android wysłanie niezbędnego powiadomienia o aktualizacji w dół hierarchii widoków do komponentu WebView.
  4. Puste miejsce: ponieważ element WebView nie otrzymuje aktualizacji, zachowuje margines z poprzedniego stanu, co powoduje pojawienie się pustego miejsca (np. zachowanie marginesu klawiatury po jej ukryciu).

Zamiast tego użyj WindowInsetsCompat.Builder, aby ustawić obsługiwane typy na zero przed przekazaniem obiektu do widoków podrzędnych. Informuje to widok WebView, że te konkretne wstawki zostały już uwzględnione, co umożliwia kontynuowanie powiadomienia w hierarchii widoków.

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();
});

Jak zrezygnować

Aby wyłączyć te nowoczesne funkcje i wrócić do starszego sposobu obsługi widocznego obszaru, wykonaj te czynności:

  1. Intercept insets: użyj setOnApplyWindowInsetsListener lub zastąp onApplyWindowInsets w podklasie WebView.

  2. Wyczyść wstawki: zwróć zużyty zestaw wstawek (np.WindowInsetsCompat.CONSUMED) od początku. To działanie całkowicie uniemożliwia przekazywanie powiadomienia wstawki do WebView, co skutecznie wyłącza nowoczesną zmianę rozmiaru obszaru wyświetlania i zmusza WebView do zachowania początkowego rozmiaru obszaru wyświetlania.