О WindowInsetsRulers

WindowInsets – это стандартный API в Jetpack Compose для обработки областей экрана, которые частично или полностью скрыты системным интерфейсом. К таким областям относятся строка состояния, панель навигации и экранная клавиатура. Вы также можете передать предопределенные WindowInsetsRulers, например SafeDrawing, в Modifier.fitInside или Modifier.fitOutside, чтобы выровнять контент по системным строкам и вырезу на экране, или создать собственные WindowInsetsRulers.

Преимущества WindowInsetsRulers

  • Не усложняет потребление ресурсов. Работает на этапе размещения макета. Это означает, что он полностью обходит цепочку потребления отступов и всегда может предоставить правильные абсолютные позиции системных строк и вырезов на экране, независимо от того, что сделали родительские макеты. Методы Modifier.fitInside и Modifier.fitOutside помогают устранять проблемы, когда родительские composable-функции неправильно используют отступы.
  • Простое избегание системных строк. Помогает контенту приложения избегать системных строк и выреза на экране. Это может быть проще, чем использовать WindowInsets напрямую.
  • Широкие возможности настройки. Разработчики могут выравнивать контент по настраиваемым линейкам и точно управлять макетами.

Недостатки WindowInsetsRulers

  • Нельзя использовать для отслеживания. Поскольку этот сигнал работает на этапе размещения, информация о позиции недоступна на более раннем этапе отслеживания.

Выравнивание контента с помощью методов Modifier

Modifier.fitInside позволяет приложениям выравнивать контент по системным строкам и вырезам на экране. Его можно использовать вместо WindowInsets. Modifier.fitOutside обычно является обратным значением Modifier.fitInside.

Например, чтобы проверить, что контент приложения не перекрывает системные строки и вырез на экране, можно использовать fitInside(WindowInsetsRulers.safeDrawing.current).

@Composable
fun FitInsideDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc...
            .fitInside(WindowInsetsRulers.SafeDrawing.current)
    )
}

В таблице ниже показано, как будет выглядеть контент приложения с заданными линейками, если использовать Modifier.fitInside или Modifier.fitOutside.

Стандартный тип линейки

Modifier.fitInside

Modifier.fitOutside

DisplayCutout

Ime

Н/Д

NavigationBars

SafeDrawing

— (используйте StatusBar, CaptionBar, NavigationBar)

StatusBar

Для использования Modifier.fitInside и Modifier.fitOutside необходимо ограничить composable-функции. Это значит, что вам нужно определить модификаторы, такие как Modifier.size или Modifier.fillMaxSize.

Некоторые правители, например Modifier.fitOutside в SafeDrawing и SystemBars, возвращают несколько правителей. В этом случае Android размещает Composable-функцию, используя одну линейку слева, сверху, справа и снизу.

Как избежать перекрытия IME с помощью Modifier.fitInside

Чтобы обрабатывать нижние элементы с помощью IME с Modifier.fitInside, передайте RectRuler, который принимает самое внутреннее значение NavigationBar и Ime.

@Composable
fun FitInsideWithImeDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.NavigationBars.current,
                    WindowInsetsRulers.Ime.current
                )
            )
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier.align(Alignment.BottomStart).fillMaxWidth()
        )
    }
}

Как избежать строки состояния и панели заголовка с помощью Modifier.fitInside

Аналогично, чтобы проверить, что верхние элементы не перекрывают строку состояния и заголовок вместе с Modifier.fitInside, передайте RectRuler, который принимает внутреннее значение StatusBars и CaptionBar.

@Composable
fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.StatusBars.current,
                    WindowInsetsRulers.CaptionBar.current
                )
            )
    )
}

Создать собственный WindowInsetsRulers

Вы можете выравнивать контент по настраиваемым линейкам. Например, рассмотрим вариант использования, когда родительская composable-функция неправильно обрабатывает отступы, что приводит к проблемам с полями в дочернем компоненте. Эту проблему можно решить разными способами, в том числе с помощью Modifier.fitInside. Также можно создать настраиваемую линейку, чтобы точно выровнять дочернюю composable-функцию, не исправляя проблему в родительской, как показано в примере и видео ниже.

@Composable
fun WindowInsetsRulersDemo(modifier: Modifier) {
    Box(
        contentAlignment = BottomCenter,
        modifier = modifier
            .fillMaxSize()
            // The mistake that causes issues downstream, as .padding doesn't consume insets.
            // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars),
            // assume it's difficult to identify this issue to see how WindowInsetsRulers can help.
            .padding(WindowInsets.navigationBars.asPaddingValues())
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier
                // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child
                // Composable without having to fix the parent upstream.
                .alignToSafeDrawing()

            // .imePadding()
            // .fillMaxWidth()
        )
    }
}

fun Modifier.alignToSafeDrawing(): Modifier {
    return layout { measurable, constraints ->
        if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val placeable = measurable.measure(constraints)
            val width = placeable.width
            val height = placeable.height
            layout(width, height) {
                val bottom = WindowInsetsRulers.SafeDrawing.current.bottom
                    .current(0f).roundToInt() - height
                val right = WindowInsetsRulers.SafeDrawing.current.right
                    .current(0f).roundToInt()
                val left = WindowInsetsRulers.SafeDrawing.current.left
                    .current(0f).roundToInt()
                measurable.measure(Constraints.fixed(right - left, height))
                    .place(left, bottom)
            }
        } else {
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

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

Проверьте, ограничены ли родительские элементы

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

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