À propos de WindowInsetsRulers

WindowInsets est l'API standard de Jetpack Compose pour gérer les zones de l'écran partiellement ou entièrement masquées par l'UI du système. Ces zones incluent la barre d'état, la barre de navigation et le clavier à l'écran. Vous pouvez également transmettre des WindowInsetsRulers prédéfinis tels que SafeDrawing à Modifier.fitInside ou Modifier.fitOutside pour aligner votre contenu avec les barres système et l'encoche, ou créer des WindowInsetsRulers personnalisés.

Avantages de WindowInsetsRulers

  • Évite la complexité de la consommation : il fonctionne pendant la phase de placement de la mise en page. Cela signifie qu'il contourne complètement la chaîne de consommation d'encarts et peut toujours fournir les positions absolues correctes des barres système et des encoches d'affichage, quelles que soient les actions des mises en page parentes. Les méthodes Modifier.fitInside ou Modifier.fitOutside sont utiles pour résoudre les problèmes lorsque les composables ancêtres consomment incorrectement les encarts.
  • Éviter facilement les barres système : il permet au contenu de votre application d'éviter les barres système et l'encoche, et peut être plus simple que d'utiliser directement WindowInsets.
  • Hautement personnalisable : les développeurs peuvent aligner le contenu sur des règles personnalisées et contrôler précisément leurs mises en page grâce à des mises en page personnalisées.

Inconvénients de WindowInsetsRulers

  • Ne peut pas être utilisé pour la mesure : comme il fonctionne pendant la phase de placement, les informations de position qu'il fournit ne sont pas disponibles pendant la phase de mesure précédente.

Aligner votre contenu avec les méthodes Modifier

Modifier.fitInside permet aux applications d'aligner le contenu sur les barres système et les encoches. Il peut être utilisé à la place de WindowInsets. Modifier.fitOutside est généralement l'inverse de Modifier.fitInside.

Par exemple, pour vérifier que le contenu de l'application évite les barres système et la découpe de l'écran, vous pouvez utiliser fitInside(WindowInsetsRulers.safeDrawing.current).

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

Le tableau suivant montre à quoi ressemblerait le contenu de votre application avec des règles prédéfinies avec Modifier.fitInside ou Modifier.fitOutside.

Type de règle prédéfini

Modifier.fitInside

Modifier.fitOutside

DisplayCutout

Ime

N/A

NavigationBars

SafeDrawing

N/A (utilisez plutôt StatusBar, CaptionBar, NavigationBar)

StatusBar

L'utilisation de Modifier.fitInside et de Modifier.fitOutside nécessite que les composables soient limités. Cela signifie que vous devez définir des modificateurs tels que Modifier.size ou Modifier.fillMaxSize.

Certaines règles, comme Modifier.fitOutside sur SafeDrawing et SystemBars, renvoient plusieurs règles. Dans ce cas, Android place le composable à l'aide d'une règle à gauche, en haut, à droite et en bas.

Éviter l'IME avec Modifier.fitInside

Pour gérer les éléments inférieurs avec un IME avec Modifier.fitInside, transmettez un RectRuler qui prend la valeur la plus profonde de NavigationBar et 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()
        )
    }
}

Éviter la barre d'état et la barre de légende avec Modifier.fitInside

De même, pour vérifier que les éléments supérieurs évitent la barre d'état et la barre de légende avec Modifier.fitInside, transmettez un RectRuler qui prend la valeur la plus interne de StatusBars et CaptionBar.

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

Créer un WindowInsetsRulers personnalisé

Vous pouvez aligner le contenu sur des règles personnalisées. Prenons l'exemple d'un composable parent qui gère mal les encarts, ce qui entraîne des problèmes de marge intérieure dans un enfant en aval. Bien que ce problème puisse être résolu d'autres manières, y compris en utilisant Modifier.fitInside, vous pouvez également créer une règle personnalisée pour aligner précisément le composable enfant sans avoir à résoudre le problème dans le parent en amont, comme illustré dans l'exemple et la vidéo suivants :

@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)
            }
        }
    }
}

La vidéo suivante montre un exemple de consommation problématique de l'encart de l'IME causée par un parent en amont dans l'image de gauche, et l'utilisation de règles personnalisées pour résoudre le problème à droite. Une marge intérieure supplémentaire s'affiche sous le composable TextField, car la marge intérieure de la barre de navigation n'a pas été utilisée par le parent. L'enfant est placé au bon endroit dans l'image de droite à l'aide d'une règle personnalisée, comme illustré dans l'exemple de code précédent.

Vérifier que les parents sont contraints

Pour utiliser WindowInsetsRulers en toute sécurité, assurez-vous que le parent fournit des contraintes valides. Les parents doivent avoir une taille définie et ne peuvent pas dépendre de la taille d'un enfant qui utilise WindowInsetsRulers. Utilisez fillMaxSize ou d'autres modificateurs de taille sur les composables parents.

De même, placer un composable qui utilise WindowInsetsRulers dans un conteneur à défilement comme verticalScroll peut entraîner un comportement inattendu, car le conteneur à défilement fournit des contraintes de hauteur illimitées, qui sont incompatibles avec la logique de la règle.