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

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

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

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

Milestone Функция добавлена Область действия
M136 Поддержка displayCutout() и systemBars() через CSS safe-area-insets. Только полноэкранные компоненты WebView.
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 и окна) применяется только к нижней части 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 пикселей. Вставка 40 пикселей будет видна и в нативном представлении, и в WebView. Оба добавляют отступ в 40 пикселей, в результате чего пользователь видит зазор в 80 пикселей вверху.

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

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

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

Избегайте фантомных полей, обнуляя вставки

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

Ниже приведен пример некорректного взаимодействия между приложением и WebView.

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

Вместо этого используйте WindowInsetsCompat.Builder, чтобы задать для обработанных типов значение 0, прежде чем передавать объект дочерним представлениям. Это сообщает 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 сохранять исходный размер визуальной области просмотра.