Перейти к содержимому
Все записи

Edge-to-edge и predictive back в Android в 2026 году: что теперь обязательно и как не сломать интерфейс

Edge-to-edge теперь обязателен для API 35+, а predictive back стал жестом по умолчанию. Практическое руководство по insets в Compose, WindowInsets и PredictiveBackHandler.

MFKAPPS 5 мин чтения

Два изменения интерфейса 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, чтобы жест не ощущался непоследовательным между экранами.

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

// По теме

Ещё из журнала

MFKAPPS 4 мин чтения

In-App Review API в Android: как просить оценку, не раздражая пользователя

Практическое руководство по In-App Review API от Google — как он на самом деле работает, когда его запускать и почему обычный попап «оцените нас» незаметно портит вашу оценку в Play Store.

#android #engineering #kotlin
MFKAPPS 5 мин чтения

Ярлыки приложений Android и плитка быстрых настроек: логирование воды без открытия приложения

Практическое руководство по динамическому ShortcutManager API и TileService в Android — как позволить действию в один тап полностью обойти приложение, и о подводных камнях, на которых спотыкается большинство реализаций.

#android #engineering #kotlin
MFKAPPS 4 мин чтения

Типы foreground-сервисов в Android в 2026 году: как выбрать тот, что реально подходит вашей функции

Практическое руководство по ограничениям типов foreground-сервисов Android — dataSync, mediaPlayback, specialUse, shortService — и как выбрать правильный, не получив ни принудительной остановки, ни отказа в проверке.

#android #engineering #kotlin