Отображайте контент от края до края в своем приложении и обрабатывайте вставки окон в Compose.

Платформа Android отвечает за отображение системного пользовательского интерфейса, такого как строка состояния и панель навигации. Этот системный пользовательский интерфейс отображается независимо от того, какое приложение использует пользователь.

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

Переход от края к краю, чтобы рисовать за системными панелями
Рис. 1. Переход от края к краю для рисования за системными полосами.

В Android 14 (уровень API 34) и более ранних версиях пользовательский интерфейс вашего приложения по умолчанию не отображается под системными панелями и не отображает вырезы.

На Android 15 (уровень API 35) и более поздних версиях ваше приложение рисуется под системными панелями и вырезами дисплея, как только ваше приложение нацелено на SDK 35. Это приводит к более удобному пользовательскому интерфейсу и позволяет вашему приложению в полной мере использовать пространство окна, доступное для это.

Отображение контента за системным пользовательским интерфейсом называется «от края до края» . На этой странице вы узнаете о различных типах вставок, о том, как работать от края до края и как использовать API-интерфейсы вставок для анимации вашего пользовательского интерфейса и обеспечения того, чтобы содержимое вашего приложения не было закрыто элементами системного пользовательского интерфейса.

Основы вставки

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

Размер системного пользовательского интерфейса и информация о том, где он расположен, задаются с помощью вставок .

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

Эти встроенные типы вставок Android доступны через WindowInsets :

WindowInsets.statusBars

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

WindowInsets.statusBarsIgnoringVisibility

Строка состояния вставляется, когда она видна. Если строки состояния в настоящее время скрыты (из-за входа в полноэкранный режим), то вставки основной строки состояния будут пустыми, но эти вставки будут непустыми.

WindowInsets.navigationBars

Вставки, описывающие панели навигации. Это панели системного пользовательского интерфейса слева, справа или снизу устройства, описывающие панель задач или значки навигации. Они могут меняться во время выполнения в зависимости от предпочтительного метода навигации пользователя и взаимодействия с панелью задач.

WindowInsets.navigationBarsIgnoringVisibility

Панель навигации вставляется, когда она видна. Если панели навигации в настоящее время скрыты (из-за входа в полноэкранный режим), то вставки основной панели навигации будут пустыми, но эти вставки будут непустыми.

WindowInsets.captionBar

Вставка, описывающая оформление окна пользовательского интерфейса системы, если оно находится в окне произвольной формы, например, верхняя строка заголовка.

WindowInsets.captionBarIgnoringVisibility

Строка заголовка вставляется, когда она видна. Если панели заголовков в настоящее время скрыты, то основные вставки панели заголовка будут пустыми, но эти вставки будут непустыми.

WindowInsets.systemBars

Объединение вставок системной панели, включающих строки состояния, панели навигации и строку заголовка.

WindowInsets.systemBarsIgnoringVisibility

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

WindowInsets.ime

Вставки, описывающие количество места внизу, которое занимает программная клавиатура.

WindowInsets.imeAnimationSource

Вставки, описывающие объем места, которое занимала программная клавиатура до текущей анимации клавиатуры.

WindowInsets.imeAnimationTarget

Вставки, описывающие объем места, которое займет программная клавиатура после текущей анимации клавиатуры.

WindowInsets.tappableElement

Тип вставок, описывающих более подробную информацию о пользовательском интерфейсе навигации и указывающих объем пространства, в котором «нажатия» будут обрабатываться системой, а не приложением. Для прозрачных панелей навигации с навигацией с помощью жестов некоторые элементы приложения можно нажимать через пользовательский интерфейс системной навигации.

WindowInsets.tappableElementIgnoringVisibility

Вставки сенсорных элементов, когда они видны. Если нажимаемые элементы в настоящее время скрыты (из-за входа в полноэкранный режим погружения), то вставки основных нажимаемых элементов будут пустыми, но эти вставки будут непустыми.

WindowInsets.systemGestures

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

WindowInsets.mandatorySystemGestures

Подмножество системных жестов, которые всегда будут обрабатываться системой и от которых нельзя отказаться с помощью Modifier.systemGestureExclusion .

WindowInsets.displayCutout

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

WindowInsets.waterfall

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

Эти типы суммируются тремя «безопасными» типами вставок, которые гарантируют, что содержимое не будет скрыто:

Эти «безопасные» типы вставок защищают контент по-разному в зависимости от вставок базовой платформы:

  • Используйте WindowInsets.safeDrawing для защиты содержимого, которое не должно отображаться под каким-либо системным пользовательским интерфейсом. Это наиболее распространенное использование вставок: для предотвращения рисования содержимого, скрытого пользовательским интерфейсом системы (частично или полностью).
  • Используйте WindowInsets.safeGestures для защиты содержимого с помощью жестов. Это позволяет избежать конфликта системных жестов с жестами приложений (например, для нижних листов, каруселей или в играх).
  • Используйте WindowInsets.safeContent как комбинацию WindowInsets.safeDrawing и WindowInsets.safeGestures чтобы обеспечить отсутствие визуального перекрытия содержимого и перекрытия жестов.

Настройка вставок

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

  1. Используйте SDK 35 или более поздней версии, чтобы обеспечить сквозную работу на Android 15 и более поздних версиях. Ваше приложение отображается за системным пользовательским интерфейсом. Вы можете настроить пользовательский интерфейс своего приложения, обрабатывая вставки.
  2. При необходимости вызовите enableEdgeToEdge() в Activity.onCreate() , что позволит вашему приложению работать без ограничений в предыдущих версиях Android.
  3. Установите android:windowSoftInputMode="adjustResize" в записи AndroidManifest.xml вашего действия. Этот параметр позволяет вашему приложению получать размер программного IME в виде вставок, которые вы можете использовать для заполнения и соответствующего размещения контента, когда IME появляется и исчезает в вашем приложении.

    <!-- in your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

Создание API

После того, как ваша активность взяла на себя управление обработкой всех вставок, вы можете использовать API-интерфейсы Compose, чтобы гарантировать, что контент не скрыт и интерактивные элементы не перекрываются с системным пользовательским интерфейсом. Эти API также синхронизируют макет вашего приложения с изменениями вставки.

Например, это самый простой метод применения вставок к содержимому всего вашего приложения:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

В этом фрагменте вставки окна safeDrawing применяются как дополнение ко всему содержимому приложения. Хотя это гарантирует, что интерактивные элементы не перекрываются с системным пользовательским интерфейсом, это также означает, что ни одно приложение не будет опираться на системный пользовательский интерфейс для достижения эффекта «от края до края». Чтобы в полной мере использовать все окно, вам необходимо точно настроить, где вставки применяются для каждого экрана или компонента за компонентом.

Все эти типы вставок анимируются автоматически с помощью анимации IME, перенесенной в API 21. Кроме того, все ваши макеты, использующие эти вставки, также автоматически анимируются при изменении значений вставки.

Существует два основных способа использования этих типов вставок для настройки компонуемых макетов: модификаторы заполнения и модификаторы размера вставки.

Модификаторы заполнения

Modifier.windowInsetsPadding(windowInsets: WindowInsets) применяет заданные вставки окна в качестве заполнения, действуя так же, как это сделал бы Modifier.padding . Например, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) применяет безопасные вставки рисования в качестве заполнения со всех четырех сторон.

Существует также несколько встроенных служебных методов для наиболее распространенных типов вставок. Modifier.safeDrawingPadding() — один из таких методов, эквивалентный Modifier.windowInsetsPadding(WindowInsets.safeDrawing) . Аналогичные модификаторы имеются и для остальных типов вставок.

Модификаторы размера вставки

Следующие модификаторы применяют количество вставок окон, устанавливая размер компонента равным размеру вставок:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Применяет начальную сторону windowInsets в качестве ширины (например, Modifier.width ).

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Применяет конечную сторону windowInsets в качестве ширины (например, Modifier.width ).

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Применяет верхнюю часть windowInsets в качестве высоты (например, Modifier.height ).

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Применяет нижнюю часть windowInsets в качестве высоты (например, Modifier.height ).

Эти модификаторы особенно полезны для изменения размера Spacer , который занимает пространство вставок:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Встроенное потребление

Модификаторы заполнения вставок ( windowInsetsPadding и помощники, такие как safeDrawingPadding ) автоматически используют часть вставок, которые применяются в качестве заполнения. При более глубоком изучении дерева композиции вложенные модификаторы заполнения вставки и модификаторы размера вставки знают, что некоторая часть вставок уже использована внешними модификаторами заполнения вставки, и избегают использования одной и той же части вставок более одного раза, что может привести к слишком много дополнительного места.

Модификаторы размера вставок также позволяют избежать повторного использования одной и той же части вставок, если вставки уже были использованы. Однако, поскольку они меняют свой размер напрямую, они сами не используют вставки.

В результате модификаторы вложенных отступов автоматически изменяют количество отступов, применяемых к каждому составному элементу.

Если посмотреть на тот же пример LazyColumn , что и раньше, LazyColumn изменяется с помощью модификатора imePadding . Внутри LazyColumn размер последнего элемента соответствует высоте нижней части системных полос:

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

Когда IME закрыт, модификатор imePadding() не применяет заполнения, поскольку IME не имеет высоты. Поскольку модификатор imePadding() не применяет отступы, никакие вставки не используются, а высота Spacer будет равна размеру нижней стороны системных полос.

Когда IME открывается, IME вставляет анимацию в соответствии с размером IME, а модификатор imePadding() начинает применять нижнее дополнение для изменения размера LazyColumn при открытии IME. Когда модификатор imePadding() начинает применять нижнее дополнение, он также начинает использовать это количество вставок. Таким образом, высота Spacer начинает уменьшаться, поскольку часть интервала для системных полос уже применена модификатором imePadding() . Как только модификатор imePadding() применяет количество нижнего заполнения, превышающее системные полосы, высота Spacer равна нулю.

Когда IME закрывается, изменения происходят в обратном порядке: Spacer начинает расширяться с нулевой высоты, как только imePadding() применяет меньше, чем нижняя часть системных полос, пока, наконец, Spacer не достигнет высоты нижней стороны система загорается после полной анимации IME.

Рисунок 2. Ленивый столбец от края до края с TextField .

Такое поведение достигается посредством взаимодействия между всеми модификаторами windowInsetsPadding , и на него можно влиять несколькими другими способами.

Modifier.consumeWindowInsets(insets: WindowInsets) также использует вставки так же, как Modifier.windowInsetsPadding , но не применяет использованные вставки в качестве заполнения. Это полезно в сочетании с модификаторами размера вставки, чтобы указать одноуровневым элементам, что определенное количество вставок уже использовано:

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) ведет себя очень похоже на версию с аргументом WindowInsets , но для использования принимает произвольное значение PaddingValues . Это полезно для информирования детей, когда заполнение или интервал обеспечивается каким-либо другим механизмом, отличным от модификаторов заполнения вставки, например обычным Modifier.padding или разделителями фиксированной высоты:

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

В тех случаях, когда необработанные вставки окна необходимы без использования, используйте значения WindowInsets напрямую или используйте WindowInsets.asPaddingValues() для возврата PaddingValues ​​вставок, на которые не влияет потребление. Однако из-за предостережений, приведенных ниже, предпочитайте использовать модификаторы заполнения оконных вставок и модификаторы размера оконных вставок везде, где это возможно.

Вставки и этапы создания Jetpack Compose

Compose использует базовые API ядра AndroidX для обновления и анимации вставок, которые используют API базовой платформы, управляющие вставками. Из-за такого поведения платформы вставки имеют особую связь с этапами Jetpack Compose .

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

Анимация клавиатуры IME с помощью WindowInsets

Вы можете применить Modifier.imeNestedScroll() к контейнеру прокрутки, чтобы автоматически открывать и закрывать IME при прокрутке до нижней части контейнера.

class WindowInsetsExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

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

Поддержка вставки для компонентов материала 3

Для простоты использования многие встроенные составные элементы Material 3 ( androidx.compose.material3 ) сами обрабатывают вставки в зависимости от того, как составные элементы размещаются в вашем приложении в соответствии со спецификациями материала.

Обработка вставок составных элементов

Ниже приведен список компонентов материала , которые автоматически обрабатывают вставки.

Панели приложений

  • TopAppBar / SmallTopAppBar / CenterAlignedTopAppBar / MediumTopAppBar / LargeTopAppBar : применяет верхнюю и горизонтальную стороны системных панелей в качестве заполнения, поскольку они используются в верхней части окна.
  • BottomAppBar : применяет нижнюю и горизонтальную стороны системных полос в качестве заполнения.

Контейнеры контента

  • ModalDrawerSheet / DismissibleDrawerSheet / PermanentDrawerSheet (содержимое внутри модального навигационного ящика): применяет вертикальные и начальные вставки к содержимому.
  • ModalBottomSheet : применяет нижние вставки.
  • NavigationBar : применяет нижнюю и горизонтальную вставки.
  • NavigationRail : применяет вертикальные и начальные вставки.

Строительные леса

По умолчанию Scaffold предоставляет вставки в качестве paddingValues параметров, которые вы можете использовать. Scaffold не применяет вставки к содержимому; эта ответственность лежит на вас. Например, чтобы использовать эти вставки с помощью LazyColumn внутри Scaffold :

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

Переопределить вставки по умолчанию

Вы можете изменить параметр windowInsets , передаваемый составному объекту, чтобы настроить его поведение. Вместо этого можно применить другой тип вставки окна или отключить его, передав пустой экземпляр: WindowInsets(0, 0, 0, 0) .

Например, чтобы отключить обработку вставок в LargeTopAppBar , установите для параметра windowInsets пустой экземпляр:

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

Взаимодействие со вставками системы View

Возможно, вам придется переопределить вставки по умолчанию, если на вашем экране код Views и Compose находится в одной и той же иерархии. В этом случае вам необходимо четко указать, какие вставки следует использовать, а какие следует игнорировать.

Например, если ваш самый внешний макет представляет собой макет Android View, вам следует использовать вставки в системе View и игнорировать их для Compose. В качестве альтернативы, если ваш внешний макет является составным, вам следует использовать вставки в Compose и соответствующим образом дополнять составные элементы AndroidView .

По умолчанию каждый ComposeView использует все вставки на уровне использования WindowInsetsCompat . Чтобы изменить это поведение по умолчанию, установите для ComposeView.consumeWindowInsets значение false .

Защита системной панели

Если ваше приложение ориентировано на SDK 35 или более поздней версии, обеспечивается обязательная сквозная поддержка . Строка состояния системы и панели навигации с помощью жестов прозрачны, но трехкнопочная панель навигации полупрозрачна.

Чтобы удалить полупрозрачную трехкнопочную фоновую защиту навигации по умолчанию, задайте для Window.setNavigationBarContrastEnforced значение false .

Ресурсы

{% дословно %} {% дословно %} {% дословно %} {% дословно %}