Вставки окон в Compose

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

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

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

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

Включение отображения в этих регионах и отображение контента за системным пользовательским интерфейсом называется переходом от края до края . На этой странице вы узнаете о различных типах вставок, о том, как выбрать сквозное использование и как использовать 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. Вызовите enableEdgeToEdge() в Activity.onCreate . Этот вызов запрашивает, чтобы ваше приложение отображалось за системным пользовательским интерфейсом. Тогда ваше приложение будет контролировать, как эти вставки используются для настройки пользовательского интерфейса.
  2. Установите 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

Как только ваша Activity возьмет на себя управление обработкой всех вставок, вы можете использовать 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 или разделителями фиксированной высоты:

@OptIn(ExperimentalLayoutApi::class)
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")
        }
    }
}

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

Рисунок 1. Анимация 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 .

Ресурсы

  • Теперь и в Android — полнофункциональное приложение для Android, полностью созданное с помощью Kotlin и Jetpack Compose.
{% дословно %} {% дословно %} {% дословно %} {% дословно %}