О WindowInsetsRulers

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

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

  • Избегает сложности использования : он работает на этапе размещения макета. Это означает, что он полностью обходит цепочку использования вставок и всегда может обеспечить правильное абсолютное положение системных панелей и вырезов экрана, независимо от действий родительских макетов. Использование методов Modifier.fitInside или Modifier.fitOutside помогает устранить проблемы, когда родительские компонуемые элементы некорректно используют вставки.
  • Легко избегайте системных панелей : это помогает содержимому вашего приложения избегать системных панелей и выреза на дисплее и может быть более простым, чем прямое использование WindowInsets .
  • Широкие возможности настройки : разработчики могут выравнивать контент по пользовательским линейкам и точно контролировать свои макеты с помощью пользовательских макетов.

Недостатки WindowInsetsRulers

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

Согласуйте свой контент с методами модификаторов

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 требует ограничения компонуемых элементов. Это означает, что необходимо определить модификаторы, такие как 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.fitInsider , передайте RectRuler , который принимает самое внутреннее значение StatusBars и CaptionBar .

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

Создание пользовательских WindowInsetsRulers

Вы можете выровнять содержимое по пользовательским линейкам. Например, рассмотрим случай, когда родительский компонуемый элемент неправильно обрабатывает вставки, что приводит к проблемам с отступами в дочернем элементе. Хотя эту проблему можно решить другими способами, в том числе с помощью Modifier.fitInside , вы также можете создать пользовательскую линейку для точного выравнивания дочернего компонуемого элемента без необходимости исправления проблемы в родительском элементе, как показано в следующем примере и видео:

@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, вызванного родительским элементом верхнего уровня (см. изображение слева), и использование пользовательских линеек для решения этой проблемы (см. изображение справа). Под компонуемым TextField отображается дополнительный отступ, поскольку отступ панели навигации не был использован родительским элементом. Дочерний элемент помещён в правильное положение на изображении справа с помощью пользовательской линейки, как показано в предыдущем примере кода.

Убедитесь, что родители ограничены

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

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