Как работают вставки окна в WebView

WebView управляет выравниванием контента с помощью двух областей просмотра: области макета (размер страницы) и визуальной области (часть страницы, которую видит пользователь). Область просмотра макета обычно статична, а область просмотра изображения динамически изменяется, когда пользователи масштабируют или прокручивают страницу, а также когда появляются элементы системного интерфейса, например виртуальная клавиатура.

Совместимость

Поддержка WebView для отступов окон со временем развивалась, чтобы поведение веб-контента соответствовало ожиданиям пользователей нативных приложений Android:

Milestone Функция добавлена Область действия
M136 Поддержка displayCutout() и systemBars() с помощью CSS-функции safe-area-insets. Только полноэкранные веб-представления.
M139 Поддержка ime() (редактора методов ввода, который представляет собой клавиатуру) с помощью изменения размера визуальной области просмотра. Все WebView.
M144 displayCutout() и systemBars(). Все WebView (независимо от того, в каком режиме они работают).

Подробнее о том, как настроить параметры конфиденциальностиWindowInsetsCompat…

Основные механики

WebView обрабатывает вставки двумя основными способами:

  • Безопасные области (displayCutout, systemBars). WebView передает эти размеры веб-контенту с помощью переменных CSS safe-area-inset-*. Это позволяет разработчикам предотвратить перекрытие интерактивных элементов (например, панелей навигации) вырезами или строками состояния.

  • Изменение размера видимой области с помощью редактора метода ввода (IME). Начиная с версии M139, редактор метода ввода (IME) напрямую изменяет размер видимой области. Этот механизм изменения размера также основан на пересечении WebView и окна. Например, в многозадачном режиме Android, если нижняя часть WebView выходит за нижнюю границу окна на 200 dp, размер видимой области просмотра будет на 200 dp меньше, чем размер WebView. Изменение размера видимой области (как для IME, так и для пересечения WebView-Window) применяется только к нижней части WebView. Этот механизм не поддерживает изменение размера при перекрытии слева, справа или сверху. Это означает, что при появлении прикрепленной клавиатуры на этих краях размер области просмотра не будет меняться.

Раньше область просмотра оставалась фиксированной, и поля ввода часто оказывались скрыты за клавиатурой. При изменении размера области просмотра видимая часть страницы по умолчанию становится прокручиваемой, поэтому пользователи могут получить доступ к скрытому контенту.

Логика границ и пересечений

WebView должен получать ненулевые значения отступов только в том случае, если элементы системного интерфейса (панели, вырезы на экране или клавиатура) напрямую перекрывают границы экрана WebView. Если WebView не перекрывает эти элементы интерфейса (например, если WebView расположен в центре экрана и не касается системных панелей), он должен получать нулевые вставки.

Чтобы переопределить логику по умолчанию и предоставить веб-контенту полные системные размеры независимо от перекрытия, используйте метод setOnApplyWindowInsetsListener и верните исходный, неизмененный объект windowInsets из прослушивателя. Указание полных размеров системы помогает обеспечить единообразие дизайна, поскольку позволяет веб-контенту выравниваться по аппаратному обеспечению устройства независимо от текущего положения WebView. Это обеспечит плавный переход, когда WebView перемещается или расширяется до краев экрана.

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

Как управлять событиями изменения размера

Поскольку теперь при появлении клавиатуры изменяется размер области просмотра, веб-код может чаще получать события изменения размера. Разработчики должны убедиться, что их код не реагирует на эти события изменения размера, сбрасывая фокус элемента. В результате возникает цикл потери фокуса и закрытия клавиатуры, который не позволяет пользователю вводить данные:

  1. Пользователь фокусируется на элементе ввода.
  2. Появляется клавиатура, и запускается событие изменения размера.
  3. Код сайта сбрасывает фокус в ответ на изменение размера.
  4. Клавиатура скрывается, потому что фокус потерян.

Чтобы избежать этого, проверьте прослушиватели на сайте и убедитесь, что изменения области просмотра не вызывают непреднамеренное срабатывание функции JavaScript blur() или очистку фокуса.

Как реализовать обработку отступов

Настройки WebView по умолчанию автоматически применяются к большинству приложений. Однако если в вашем приложении используются специальные макеты (например, если вы добавили собственные отступы для учета строки состояния или клавиатуры), вы можете улучшить взаимодействие веб-контента и встроенного интерфейса, используя следующие подходы. Если в вашем нативном интерфейсе к контейнеру применяется отступ на основе WindowInsets, вам необходимо правильно управлять этими вставками до того, как они попадут в WebView, чтобы избежать двойного отступа.

Двойное заполнение – это ситуация, когда в нативном макете и веб-контенте используются одинаковые размеры отступов, что приводит к избыточному интервалу. Например, если у телефона панель статуса высотой 40 пикселей, И нативный вид, и WebView видят отступ в 40 пикселей. Оба добавляют отступ в 40 пикселей, поэтому пользователь увидит сверху отступ в 80 пикселей.

Подход обнуления

Чтобы избежать двойного отступа, убедитесь, что после того, как в представлении нативного объявления для отступа был использован размер вставки, вы сбросили этот размер до нуля с помощью Insets.NONE в новом объекте WindowInsets, прежде чем передавать измененный объект вниз по иерархии View в WebView.

При добавлении отступов к родительскому представлению обычно следует использовать подход с обнулением, задавая Insets.NONE вместо WindowInsetsCompat.CONSUMED. Возврат значения WindowInsetsCompat.CONSUMED может быть уместен в некоторых ситуациях. Однако могут возникнуть проблемы, если обработчик приложения изменяет вставки или добавляет собственные отступы. У подхода с обнулением таких ограничений нет.

Как избежать появления лишних полей, задав нулевые отступы

Если вы используете вставки, когда приложение ранее передавало неиспользованные вставки, или если вставки меняются (например, когда скрывается клавиатура), использование вставок не позволяет WebView получать необходимые уведомления об обновлениях. Из-за этого WebView может сохранять фантомные отступы из предыдущего состояния (например, отступы для клавиатуры после того, как она была скрыта).

В примере ниже показано, как нарушается взаимодействие между приложением и WebView:

  1. Исходное состояние. Приложение изначально передает в WebView неиспользованные отступы (например, displayCutout() или systemBars()), которые внутренне применяются к веб-контенту.
  2. Изменение состояния и ошибка. Если приложение меняет состояние (например, клавиатура скрывается) и решает обработать полученные вставки, возвращая WindowInsetsCompat.CONSUMED.
  3. Уведомление заблокировано. Использование отступов не позволяет системе Android отправить необходимое уведомление об обновлении по иерархии View в WebView.
  4. Фантомные отступы. Поскольку WebView не получает обновление, он сохраняет отступы из предыдущего состояния, что приводит к появлению фантомных отступов (например, отступов клавиатуры после того, как она была скрыта).

Вместо этого используйте WindowInsetsCompat.Builder, чтобы задать нулевые значения для обрабатываемых типов, прежде чем передавать объект дочерним представлениям. Это позволяет WebView понять, что определенные отступы уже учтены, и разрешить уведомлению продолжить движение вниз по иерархии View.

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

Как отключить сохранение в файле cookie

Чтобы отключить эти современные функции и вернуться к устаревшему способу обработки области просмотра, выполните следующие действия:

  1. Перехват вставок. Используйте setOnApplyWindowInsetsListener или переопределите onApplyWindowInsets в подклассе WebView.

  2. Очистить вставки. Возвращает использованный набор вставок (например, WindowInsetsCompat.CONSUMED) с начала. Это действие предотвращает распространение встроенного уведомления на WebView, отключает изменение размера современного окна просмотра и заставляет WebView сохранять исходный размер окна просмотра.