Edge-to-edge и predictive back в Android в 2026 году: что теперь обязательно и как не сломать интерфейс
Edge-to-edge теперь обязателен для API 35+, а predictive back стал жестом по умолчанию. Практическое руководство по insets в Compose, WindowInsets и PredictiveBackHandler.
Два изменения интерфейса Android в этом цикле перестали быть опциональными. Приложения, нацеленные на API 35+, получают edge-to-edge принудительно — вы больше не можете отказаться и позволить системе рисовать за вас непрозрачные панели. А predictive back — жест смахивания назад, который показывает превью того, куда вы попадёте, ещё до подтверждения, — теперь является взаимодействием по умолчанию на устройствах с жестовой навигацией, а не экспериментальным флагом. Ни то, ни другое не редизайн. Оба изменения заметно сломают интерфейс, не рассчитанный на них, и режим отказа одинаков в обоих случаях: всё прекрасно работает на эмуляторе, который вы тестировали, и выглядит сломанным на реальном телефоне с вырезом или с отключённой трёхкнопочной навигацией.
Вот что на самом деле изменилось и какой код Compose удерживает экраны пяти приложений корректными при обоих изменениях.
Edge-to-edge больше не выбор
До API 35 enableEdgeToEdge() был тем, что вы включали для конкретного экрана — приятной опцией для приложений, которые хотели, чтобы контент проходил под полупрозрачной строкой состояния. Таргет на API 35 убирает этот выбор: поведение Window.setDecorFitsSystemWindows(false) становится принудительным, системные панели становятся прозрачными, и ваш корневой контент рисуется за ними независимо от того, вызывали вы что-либо или нет.
Практический эффект таков: любая раскладка, которая предполагала, что строка состояния и панель навигации резервируют собственное пространство, теперь видит, что контент начинается с физического пикселя ноль. Заголовок верхней панели приложения оказывается под часами. Иконки нижней панели навигации оказываются под жестовой «таблеткой». Это не проблема визуальной полировки — на реальном устройстве это означает, что пользователи не могут коснуться нижнего ряда вашего интерфейса.
Решение — не «добавить padding везде». Это применение WindowInsets на правильной границе, один раз, чтобы системные панели резервировали пространство именно там, где оно нужно контенту, и больше нигде.
setContent {
AppTheme {
Scaffold(
contentWindowInsets = WindowInsets.safeDrawing,
topBar = { TopAppBar(title = { Text("Stocky") }) },
) { padding ->
LazyColumn(contentPadding = padding) {
items(pantryItems) { PantryRow(it) }
}
}
}
}
Scaffold уже учитывает верхнюю и нижнюю панели, которые ему переданы, поэтому большинству экранов нужно лишь подключить этот единственный параметр. Ловушка — сделать это дважды: Scaffold применяет safe-drawing insets к своему значению padding, и если дочерний элемент тоже вызывает .systemBarsPadding() поверх этого, вы получаете двойной отступ под строкой состояния — пустую полосу, которая выглядит как баг дизайна, а на самом деле является багом в коде insets.
Где insets нужно обрабатывать вручную
Не каждый экран проходит через Scaffold. Три места, которые мне пришлось чинить вручную в Granyn, Stocky и Mintly:
- Нижние листы (bottom sheets) и диалоги.
ModalBottomSheetрисует собственную поверхность вне padding родительскогоScaffold, поэтому его контенту нужен собственный.navigationBarsPadding()на самой внутренней колонке, иначе последняя строка окажется под жестовой панелью. - Клавиатура.
WindowInsets.ime— это отдельный inset отnavigationBars, и он анимируется по мере открытия и закрытия клавиатуры. Текстовому полю, закреплённому внизу экрана, нужен.imePadding(), а не.navigationBarsPadding()— они совпадают, когда клавиатура закрыта, и расходятся в момент её открытия. - Полноэкранные изображения или заголовки. Когда вы намеренно хотите, чтобы контент рисовался под строкой состояния — галерея скриншотов, hero-изображение — применяйте insets к интерактивным элементам управления поверх (кнопка назад, иконка «поделиться») через
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top)), а не добавляйте padding всему контейнеру, иначе вы потеряете нужный эффект.
Единственная привычка, которая ловит большинство таких проблем раньше, чем это сделает пользователь: включите жестовую навигацию и кастомизацию строки состояния «Demo mode» в эмуляторе и проверяйте каждый экран с включённой симуляцией выреза. Трёхкнопочная навигация и простая строка состояния скрывают почти все баги insets, которые вы рискуете выпустить.
Predictive back: от жёсткого обрыва к превью
Другое изменение — поведенческое, а не про раскладку. Predictive back позволяет системе показывать, куда приведёт жест назад — уменьшающееся превью нижележащего экрана — ещё до того, как пользователь отпустит палец, чтобы он мог увидеть пункт назначения и отменить действие на середине жеста. Приложения, которые ещё не подключили это, по-прежнему получают плоскую, мгновенную смену экрана; жест работает, но выглядит как резкий обрыв рядом с любым другим приложением на устройстве, поддерживающим predictive back.
Подключение требует флага в манифесте плюс callback в Compose:
<!-- AndroidManifest.xml -->
<application android:enableOnBackInvokedCallback="true">
PredictiveBackHandler(enabled = showDetail) { progress ->
try {
progress.collect { backEvent ->
// backEvent.progress: 0f (начало) → 1f (подтверждено)
scale = 1f - (backEvent.progress * 0.1f)
}
showDetail = false // жест подтверждён
} catch (e: CancellationException) {
scale = 1f // жест отменён — вернуть исходное состояние
}
}
PredictiveBackHandler (из androidx.activity.compose) даёт вам Flow событий прогресса вместо единственного callback — это как раз то место, где спотыкаются те, кто пришёл с BackHandler. Вы собираете его на протяжении всего жеста, запускаете анимацию на основе backEvent.progress, а CancellationException при отменённом свайпе — это нормальный путь выхода, а не ошибка: так система сообщает, что пользователь отпустил палец до подтверждения. Я использовал именно это на экране деталей сессии в Mintly: вид таймера теперь слегка уменьшается и открывает список позади себя по мере свайпа назад, и аккуратно возвращается к полному размеру, если вы отпустите раньше времени, вместо того чтобы экран просто исчезал.
Что действительно стоит проверить перед публикацией
- Каждый экран корректно отображается при включённой жестовой навигации и отключённой трёхкнопочной — в этом режиме поставляется большинство новых устройств.
- Нет задвоенных отступов под строкой состояния (ловушка
Scaffold+ ручной.systemBarsPadding()выше). - Нижние листы и снекбары не заходят под жестовую панель на устройстве без аппаратных кнопок навигации.
- Текстовые поля рядом с нижней частью экрана используют
.imePadding()и не дёргаются при открытии клавиатуры. - Хотя бы на одном экране с реальной навигацией (а не только на первом, который вы тестируете) подключён predictive back, чтобы жест не ощущался непоследовательным между экранами.
Ничего из этого не сложно, как только вы с этим столкнётесь. Просто это незаметно на стандартном эмуляторе с трёхкнопочной навигацией — а это как раз та конфигурация, которую большинство из нас оставляет по умолчанию, — и именно поэтому это чаще, чем следовало бы, оказывается сломанным в релизе.
// По теме
Ещё из журнала
In-App Review API в Android: как просить оценку, не раздражая пользователя
Практическое руководство по In-App Review API от Google — как он на самом деле работает, когда его запускать и почему обычный попап «оцените нас» незаметно портит вашу оценку в Play Store.
Ярлыки приложений Android и плитка быстрых настроек: логирование воды без открытия приложения
Практическое руководство по динамическому ShortcutManager API и TileService в Android — как позволить действию в один тап полностью обойти приложение, и о подводных камнях, на которых спотыкается большинство реализаций.
Типы foreground-сервисов в Android в 2026 году: как выбрать тот, что реально подходит вашей функции
Практическое руководство по ограничениям типов foreground-сервисов Android — dataSync, mediaPlayback, specialUse, shortService — и как выбрать правильный, не получив ни принудительной остановки, ни отказа в проверке.