Всем привет! Мы – Саша Королёв и Юля Трусова, инженеры в Design System Авито. Наша команда работает над качеством интерфейсов: актуальностью, предсказуемостью, доступностью. В этой статье рассказываем про наш опыт внедрения edge-to-edge в мобильном приложении Avito для Android.
Материал будет особенно вам интересен, если ваше приложение не использовало режим edge-to-edge, но ввиду последних требований от Google по переходу на target SDK 35, появилась в этом необходимость. Ведь данное обновление применяет режим по умолчанию без возможности его отключить. Из статьи вы узнаете, с какими сложностями столкнулись мы как участники большого проекта при интеграции данного режима в масштабный проект с не одной сотней экранов.

Что внутри статьи:
Что такое Edge-to-edge?
Edge-to-edge («от края до края») — это режим в Android, при котором приложение отрисовывается под системными панелями — строкой состояния (status bar), вырезами дисплея или «челкой» (display cutout) и панелью навигации (navigation bar), — используя весь экран смартфона.
Главные критерии соответствия приложения режиму edge-to-edge – это наличие:
прозрачного Status Bar;
прозрачного Navigation Bar для девайсов с навигацией с помощью жестов и полупрозрачного Navigation Bar для трехкнопочной панели.

Разработка
Причины поддержки edge-to-edge
Жили бы и дальше себе спокойно без этого режима, но почему вдруг появилась потребность его поддержать? А все из-за перехода на target SDK 35 и идущего с ним в комплекте так называемого edge-to-edge enforcement – насильного применения режима «от края до края».
Обратите внимание, что насильно данный режим применяется только для девайсов с версией Android 15+ (Android SDK 35+), то есть поддерживать edge-to-edge для предыдущих версий Android SDK (< 35) не требуется для перехода на target SDK 35. Однако сам режим edge-to-edge доступен для использования начиная с Android SDK 21+, ввиду чего при желании вы можете также распространить режим и на версии < 35.
Feature-toggle для безопасного перехода
Как поддержать edge-to-edge в своем приложении, можно прочитать в официальной документации, пересказывать это нет смысла, однако в этой статье мы делимся опытом Авито, как большого проекта с огромным количеством экранов, их у нас почти 1000.
Напомним, что основная причина перехода на edge-to-edge – это требование Google при поднятии target SDK 35. В случае, если ваше приложение, как и приложение Авито, до поднятия target SDK не использовало режим edge-to-edge, к вопросу поднятия версии стоит отнестись с большим вниманием и трепетом. Ведь без должных обработок WindowInsets (вставок системных панелей) приложение может просто «разъехаться», а часть критичного для бизнеса функционала станет некликабельной и неюзабельной.

Режим Edge-to-Edge на самом деле имеет не так много кейсов применения: в основном он используется для красивого скролла длинных списков данных. В большинстве же экранов, особенно где нет списков, как такового преимущества от использования режима «от края до края» нет.
Именно поэтому для первой итерации перехода на этот режим нашей задачей как большого проекта было не сломать то, что уже работает: мы не планировали поддержать красивый edge-to-edge для всех списков на всех экранах, а хотели сделать так, чтобы UI-элементы наших экранов не съехали за системные панели, создав неприятный и дефективный внешний вид приложения. А в худшем случае они могли и вовсе отключить возможность пользователю прокликать часть критичного бизнес-функционала.
Чтобы наш переход на target SDK 35 был безопасным и безболезненным, мы продумали план:
На действующем target SDK 34 проработать режим edge-to-edge под remote feature-toggle. Поднимать target SDK 35 до того, как мы убедимся, что нет проблем с edge-to-edge, небезопасно по той причине, что при target SDK 35 нет возможности отключить edge-to-edge в рантайме, следовательно, нет возможности управлять функционалом через remote feature-toggle.
Включить данный тоггл по дефолту в девелопе, чтобы обнаружить первым делом проблемы в debug-версии приложения среди разработчиков.
После устранения всех известных проблем включить тоггл в релизе, чтобы убедиться, что нет критичных проблем, ломающих бизнес-сценарии. В случае выявления критичных проблем, всегда есть возможность оперативно отключить тоггл.
Данный подход сделал наш переход на edge-to-edge управляемым и подконтрольным с помощью remote feature-toggle.

Обработка WindowInsets на экранах
Исторически сложилось, что наше приложение не является single-activity, т.к. проект зародился задолго до того, как появились Fragment’ы. На данный момент у нас нет острой необходимости переводить все на single-activity, так как преимуществ от этого не видим. Именно поэтому при переходе на edge-to-edge нам было важно учесть все точки входа в UI: Activity, Fragment, Dialog, BottomSheet.
Теперь опишем механизм применения edge-to-edge под feature-toggle.
В Android-проекте Авито все точки входа в экраны наследуются от базовых сущностей, таких как BaseActivity, BaseFragment и других, поэтому обработки WindowInsets мы решили добавить именно в эти базовые сущности, чтобы они по дефолту применились для всех экранов.
Для BaseActivity в методе onCreate обрабатываем отступы для android.R.id.content. Примерно код может выглядеть так:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content)) { v, insets ->
...
}
}
Для BaseFragment в методе onViewCreated обрабатываем отступы для созданной view. Примерно код может выглядеть так:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
...
}
}
Обработка цветов системных панелей
Также стоит обратить внимание на то, что в режиме edge-to-edge теряется возможность указывать цвет для системных панелей – они становятся прозрачными (или полупрозрачными в случае с трехкнопочной панелью навигации). Чтобы сымитировать такое поведение, мы делаем force-выставление прозрачных цветов в методах применения темы Activity, так как именно в этих методах читаются такие атрибуты, как android:navigationBarColor & android:statusBarColor:
abstract class BaseActivity : AppCompatActivity() {
override fun setTheme(theme: Resources.Theme?) {
super.setTheme(theme)
overrideSystemBarsColors()
}
override fun setTheme(resId: Int) {
super.setTheme(resId)
overrideSystemBarsColors()
}
private fun overrideSystemBarsColors() {
window.apply {
statusBarColor = Color.TRANSPARENT
navigationBarColor = Color.TRANSPARENT
}
}
}
Обработка клавиатуры
В режиме edge-to-edge важно предусмотреть логику добавления отступов при появлении на экране клавиатуры. В обычном режиме регулировать поведение экранов при появлении клавиатуры можно через флаг android:windowSoftInputMode для activity в манифесте. Однако в режиме «от края до края» данный флаг не учитывается, а клавиатура является системной панелью, отступ которой важно обработать в целях поддержания корректности внешнего вида экрана.
Интересный факт, что для Android SDK 30+ можно обработать анимированный показ клавиатуры, чтобы не было скачка при появлении клавиатуры, а произошло плавное поднятие UI-элементов. Данный механизм использует те же самые WindowInsets и не конфликтует с основным коллебеком setOnApplyWindowInsets. Его-то мы и используем:
fun View.handleImeInsetIfNeeded() {
var originalPaddingBottom: Int? = null
ViewCompat.setWindowInsetsAnimationCallback(
this,
object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
override fun onStart(
animation: WindowInsetsAnimationCompat,
bounds: WindowInsetsAnimationCompat.BoundsCompat
): WindowInsetsAnimationCompat.BoundsCompat {
// Эта функция вызывается в начале анимации и уже после применения системных инсетов,
// поэтому оригинальный паддинг сохраняем тут единожды,
// чтобы при скрытии клавиатуры его восстановить
if (originalPaddingBottom == null) {
originalPaddingBottom = this@handleImeInsetIfNeeded.paddingBottom
}
return bounds
}
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: MutableList<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
// Если нет анимации клавиатуры, выйти из метода
runningAnimations
.find { it.typeMask and WindowInsetsCompat.Type.ime() != 0 }
?: return insets
// Берем инсет клавиатуры на данном шаге анимации
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
this@handleImeInsetIfNeeded.updatePadding(
// Берем максимальное от оригинального паддинга или клавиатуры,
// чтобы при скрытии клавиатуры выставленный ранее паддинг системных панелей не занулился
bottom = max(imeHeight, originalPaddingBottom ?: 0),
)
return insets
}
}
)
OptOut-флаг
Вместе с тем, как добавить force-применение edge-to-edge режима при переходе на target SDK 35, в новой версии Android SDK 35 также добавили и малоизвестный атрибуты темы R.attr.windowOptOutEdgeToEdgeEnforcement, позволяющий отключить насильное применение edge-to-edge при поднятии target SDK 35. Мы настоятельно не рекомендуем использовать этот флаг, так как это лишь временное решение, позволяющее отсрочить поддержание edge-to-edge-режима до следующего target SDK 36, и Google нас уже об этом предупреждает в официальной документации. С этим флагом уже не будет возможности также динамически управлять режимом edge-to-edge в рантайме, следовательно поддержать edge-to-edge лучше всего, оставаясь на target SDK 34.

Тестирование
В рамках первой итерации тестирования нам было важно:
найти и кластеризовать основные типы багов, которые были у нас в приложении;
прогнать текущие автотесты и запланировать время на их фикс;
описать подход к тестированию edge-to-edge для остальных QA в компании, которые будут проводить полный регресс.
Как выбрать девайсы?
Мир Android-девайсов огромен, поэтому мы обратились за помощью к ChatGPT и вот, что он предложил:
Девайсы с челкой (notch):
• Google Pixel 3 XL: широкая челка.
• OnePlus 6/6T: каплевидная челка.
• Samsung Galaxy S10/S20/S21: отверстие для камеры в дисплее (punch-hole).
• Huawei P30 Pro: каплевидная челка.
• Xiaomi Mi 8: широкая челка.
• Essential Phone (PH-1): очень маленькая челка.
Девайсы с панелью навигации:
• Samsung Galaxy S9/S10/S21 (или новее): устройства с системой жестов и скрываемой панелью навигации.
• Google Pixel (серии 3–7): панель навигации с жестами.
• OnePlus 7 Pro/8 Pro/9 Pro: полноэкранный интерфейс с жестами.
• Xiaomi Mi Mix 3: полноэкранный режим без панели навигации (с использованием жестов).
Другие устройства для тестирования разных форм дисплея:
• Samsung Galaxy Z Fold/Flip: складные экраны.
• LG Wing: дисплей с уникальной ориентацией.
• Sony Xperia 1 III: узкий и высокий дисплей с соотношением 21:9.
Дальше мы уже выбирали нужные устройства из того пула, что был у нас в юните.
Если у вас на руках нет нужных девайсов, можно воспользоваться альтернативными вариантами:
эмуляторами(подойдет не для всех кейсов);
облачными сервисами (например, BrowserStack);
попросить девайс у коллег;
использовать функцию различных отображений челки в настройках разработчика на девайсе.
На что стоит обратить внимание
Проверьте, что:
элементы интерфейса (кнопки, текст, изображения) не выходят за пределы экрана, а также корректно отображаются даже на устройствах с выемками или закругленными углами;
контент не перекрывается выемками и прорезями для камер;
элементы интерфейса не пересекаются со статусной строкой и панелью навигации;
панели скрываются/появляются в нужный момент, не влияя на внешний вид интерфейса;
анимации интерфейса и переходы (например, переходы между экранами) плавные, особенно на старых устройствах;
приложение корректно реагирует на касания в любом месте экрана, в том числе на краях и углах экрана.
Кроме этого, стоит выделить время на проверки в landscape-режиме и темной теме, если вы это поддерживаете.

Примеры из практики
Теория - это прекрасно, а теперь рассмотрим реальные примеры, с которыми мы столкнулись на практике.

Слева - экран смещен вверх - контент перекрыт выемками и прорезями для камер
В центре - навигационная панель не стала прозрачной/полупрозрачной
Справа - не проскроливаются списки до конца при открытой клавиатуре (в том числе и в landscape)
Клавиатура перекрывает элементы (например, боттомшит скрыт за клавиатурой).Для этого кейса визуального примера нет.


Заключение
Спасибо вам за уделенное статье время! Делитесь своим опытом работы с edge-to-edge в комментариях. Всем добра и меньше багов!
Подробнее о том, какие задачи решают инженеры Авито, — на нашем сайте и в телеграм-канале AvitoTech. А вот здесь — свежие вакансии в нашу команду.