Encarts de fenêtre dans Compose

La plate-forme Android est chargée de dessiner l'UI du système, comme la barre d'état et la barre de navigation. Cette UI système s'affiche quelle que soit l'application utilisée par l'utilisateur. WindowInsets fournit des informations sur l'UI du système pour s'assurer que votre application s'affiche dans la zone appropriée et que votre UI n'est pas masquée par l'UI du système.

Passer d'un bord à l'autre pour dessiner derrière les barres système
Figure 1. Utiliser l'écran bord à bord pour dessiner derrière les barres système

Par défaut, l'UI de votre application est limitée à l'interface utilisateur du système, comme la barre d'état et la barre de navigation. Cela garantit que le contenu de votre application n'est pas masqué par les éléments de l'UI du système.

Toutefois, nous recommandons aux applications d'activer l'affichage dans les zones où l'UI du système est également affichée. Cela offrira une expérience utilisateur plus fluide et permettra à votre application de profiter pleinement de l'espace de fenêtre disponible. Cela permet également aux applications de s'animer avec l'UI du système, en particulier lors de l'affichage et du masquage du clavier virtuel.

L'activation de l'affichage dans ces régions et de l'affichage du contenu derrière l'interface utilisateur du système s'appelle une transition bord à bord. Sur cette page, vous découvrirez les différents types d'encarts, comment activer la transition bord à bord et comment utiliser les API d'encart pour animer votre interface utilisateur et éviter de masquer des parties de votre application.

Principes de base des encarts

Lorsqu'une application est bord à bord, vous devez vous assurer que le contenu et les interactions importants ne sont pas masqués par l'UI du système. Par exemple, si un bouton est placé derrière la barre de navigation, l'utilisateur ne pourra peut-être pas cliquer dessus.

La taille de l'UI du système et les informations sur son emplacement sont spécifiées à l'aide d'encarts.

Chaque partie de l'interface utilisateur du système possède un type d'encart correspondant qui décrit sa taille et son emplacement. Par exemple, les encarts de la barre d'état indiquent la taille et la position de la barre d'état, tandis que les encarts de la barre de navigation indiquent la taille et la position de la barre de navigation. Chaque type d'encart comporte quatre dimensions en pixels: haut, gauche, droite et bas. Ces dimensions spécifient la portée de l'UI du système par rapport aux côtés correspondants de la fenêtre de l'application. Pour éviter tout chevauchement avec ce type d'UI du système, l'UI de l'application doit donc être en incrustation.

Ces types d'encarts Android intégrés sont disponibles via WindowInsets:

WindowInsets.statusBars

Encarts décrivant les barres d'état. Il s'agit des barres supérieures de l'interface utilisateur du système contenant des icônes de notification et d'autres indicateurs.

WindowInsets.statusBarsIgnoringVisibility

Des encarts indiquent le moment où ils sont visibles. Si les barres d'état sont actuellement masquées (en raison du passage en mode plein écran immersif), les encarts de la barre d'état principale seront vides, mais ils ne seront pas vides.

WindowInsets.navigationBars

Encarts décrivant les barres de navigation. À gauche, à droite ou en bas de l'appareil, ces barres d'interface utilisateur décrivent la barre des tâches ou les icônes de navigation. Ils peuvent changer au moment de l'exécution en fonction de la méthode de navigation préférée de l'utilisateur et de l'interaction avec la barre des tâches.

WindowInsets.navigationBarsIgnoringVisibility

La barre de navigation s'affiche dans des encarts lorsqu'ils sont visibles. Si les barres de navigation sont actuellement masquées (en raison du passage en mode plein écran immersif), les encarts de la barre de navigation principale seront vides, mais ils ne seront pas vides.

WindowInsets.captionBar

Encart décrivant la décoration de la fenêtre de l'UI du système dans une fenêtre de format libre, comme la barre de titre supérieure.

WindowInsets.captionBarIgnoringVisibility

La barre de sous-titres s'affiche dans des encarts lorsqu'ils sont visibles. Si les barres de sous-titres sont actuellement masquées, les encarts de la barre de sous-titres principale seront vides, mais ils ne le seront pas.

WindowInsets.systemBars

Union des encarts de la barre système, qui incluent les barres d'état, les barres de navigation et la barre de sous-titres.

WindowInsets.systemBarsIgnoringVisibility

Des encarts indiquent le moment où ils sont visibles. Si les barres système sont actuellement masquées (en raison du passage en mode plein écran immersif), les encarts de la barre système principale seront vides, mais ils ne le seront pas.

WindowInsets.ime

Encarts décrivant la quantité d'espace en bas occupée par le clavier virtuel.

WindowInsets.imeAnimationSource

Encarts décrivant l'espace occupé par le clavier virtuel avant l'animation clavier en cours.

WindowInsets.imeAnimationTarget

Encarts décrivant l'espace occupé par le clavier virtuel après l'animation clavier en cours.

WindowInsets.tappableElement

Type d'encarts décrivant des informations plus détaillées sur l'interface utilisateur de navigation, donnant l'espace où les "appuis" seront gérés par le système, et non par l'application. Pour les barres de navigation transparentes avec navigation par gestes, il est possible d'appuyer sur certains éléments de l'application via l'UI de navigation système.

WindowInsets.tappableElementIgnoringVisibility

Encarts d'élément sur lesquels l'utilisateur peut appuyer dessus lorsqu'ils sont visibles. Si les éléments tactiles sont actuellement masqués (en raison du passage en mode plein écran immersif), les encarts principaux des éléments tactiles seront vides, mais ils ne seront pas vides.

WindowInsets.systemGestures

Encarts représentant le nombre d'encarts où le système interceptera les gestes pour la navigation. Les applications peuvent spécifier manuellement la gestion d'un nombre limité de ces gestes via Modifier.systemGestureExclusion.

WindowInsets.mandatorySystemGestures

Sous-ensemble des gestes système qui seront toujours gérés par le système et qui ne peuvent pas être désactivés via Modifier.systemGestureExclusion.

WindowInsets.displayCutout

Encarts représentant la quantité d'espacement nécessaire pour éviter tout chevauchement avec une encoche d'écran (encoche ou petit trou d'épingle).

WindowInsets.waterfall

Encarts représentant les zones incurvées d'un affichage en cascade. Un écran en cascade présente des zones incurvées le long des bords de l'écran, où l'écran commence à s'enrouler sur les côtés de l'appareil.

Ces types sont résumés en trois types d'encarts "sécurisés" qui garantissent que le contenu n'est pas masqué:

Ces types d'encarts "sécurisés" protègent le contenu de différentes manières, en fonction des encarts de la plate-forme sous-jacente:

  • Utilisez WindowInsets.safeDrawing pour protéger le contenu qui ne doit pas être affiché sous une UI du système. Il s'agit de l'utilisation la plus courante des encarts, qui permet d'éviter de dessiner du contenu masqué (en partie ou complètement) par l'interface utilisateur du système.
  • Utilisez WindowInsets.safeGestures pour protéger le contenu à l'aide de gestes. Cela évite les conflits entre les gestes système et les gestes de l'application (comme ceux des bottom sheet, des carrousels ou des jeux).
  • Utilisez WindowInsets.safeContent en combinaison de WindowInsets.safeDrawing et WindowInsets.safeGestures pour vous assurer que le contenu n'a pas de chevauchement visuel ni de chevauchement de gestes.

Configuration des encarts

Pour permettre à votre application de contrôler entièrement l'emplacement de dessin du contenu, suivez ces étapes de configuration. Sans ces étapes, votre application risque de dessiner des couleurs noires ou unies derrière l'interface utilisateur du système, ou de ne pas s'animer de manière synchrone avec le clavier virtuel.

  1. Appelez enableEdgeToEdge() dans Activity.onCreate. Cet appel demande que votre application s'affiche derrière l'UI du système. Votre application contrôle ensuite la façon dont ces encarts sont utilisés pour ajuster l'interface utilisateur.
  2. Définissez android:windowSoftInputMode="adjustResize" dans l'entrée AndroidManifest.xml de votre activité. Ce paramètre permet à votre application de recevoir la taille de l'IME du logiciel sous forme d'encarts, que vous pouvez utiliser pour remplir et mettre en page le contenu de manière appropriée lorsque l'IME apparaît et disparaît dans votre application.

    <!-- 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 Compose

Une fois que votre activité a pris le contrôle de la gestion de tous les encarts, vous pouvez utiliser les API Compose pour vous assurer que le contenu n'est pas masqué et que les éléments interactifs ne se chevauchent pas avec l'UI du système. Ces API synchronisent également la mise en page de votre application avec les modifications des encarts.

Par exemple, il s'agit de la méthode la plus basique pour appliquer les encarts au contenu de l'ensemble de votre application:

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

    enableEdgeToEdge()

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

Cet extrait applique les encarts de la fenêtre safeDrawing en tant que marge intérieure autour de l'ensemble du contenu de l'application. Bien que cela garantit que les éléments interactifs ne se superposent pas à l'UI du système, cela signifie également qu'aucune application ne s'affichera derrière l'interface utilisateur du système pour obtenir un effet bord à bord. Pour exploiter pleinement l'intégralité de la fenêtre, vous devez ajuster l'emplacement des encarts, écran par écran ou composant par composant.

Tous ces types d'encarts sont animés automatiquement avec des animations IME rétroportées vers l'API 21. Par extension, toutes les mises en page qui utilisent ces encarts sont également animées automatiquement à mesure que les valeurs des encarts changent.

Il existe deux façons principales d'utiliser ces types d'encarts pour ajuster vos mises en page modulables: les modificateurs de marge intérieure et les modificateurs de taille d'encart.

Modificateurs de marge intérieure

Modifier.windowInsetsPadding(windowInsets: WindowInsets) applique les encarts de fenêtre donnés en tant que marge intérieure, agissant comme le ferait Modifier.padding. Par exemple, Modifier.windowInsetsPadding(WindowInsets.safeDrawing) applique les encarts de dessin sécurisés en tant que marge intérieure sur les quatre côtés.

Il existe également plusieurs méthodes utilitaires intégrées pour les types d'encarts les plus courants. Modifier.safeDrawingPadding() est l'une de ces méthodes, équivalente à Modifier.windowInsetsPadding(WindowInsets.safeDrawing). Il existe des modificateurs analogues pour les autres types d'encarts.

Modificateurs de taille d'encart

Les modificateurs suivants appliquent une quantité d'encarts de fenêtre en définissant la taille du composant sur celle des encarts:

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

Applique le côté début des windowInsets en tant que largeur (par exemple, Modifier.width).

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

Applique le côté final des windowInsets en tant que largeur (par exemple, Modifier.width).

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

Applique la partie supérieure des windowInsets en hauteur (par exemple, Modifier.height).

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

Applique la partie inférieure des windowInsets en hauteur (par exemple, Modifier.height).

Ces modificateurs sont particulièrement utiles pour dimensionner une Spacer qui occupe l'espace des encarts:

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

Consommation d'encarts

Les modificateurs de marge intérieure d'encart (windowInsetsPadding et les assistants tels que safeDrawingPadding) consomment automatiquement la partie des encarts appliquée en tant que marge intérieure. En allant plus loin dans l'arborescence de composition, les modificateurs de marge intérieure d'encart imbriqués et les modificateurs de taille d'encart savent qu'une partie des encarts ont déjà été utilisés par les modificateurs de marge intérieure d'encart externe. Ils évitent donc d'utiliser la même partie plusieurs fois, ce qui entraînerait trop d'espace supplémentaire.

Les modificateurs de taille d'encart évitent également d'utiliser la même partie d'encarts plusieurs fois si ceux-ci ont déjà été utilisés. Toutefois, comme ils modifient directement leur taille, ils ne consomment pas eux-mêmes les encarts.

Par conséquent, les modificateurs de marge intérieure d'imbrication modifient automatiquement la quantité de marge intérieure appliquée à chaque composable.

En examinant le même exemple LazyColumn que précédemment, LazyColumn est redimensionné par le modificateur imePadding. Dans LazyColumn, le dernier élément est dimensionné pour correspondre à la hauteur du bas des barres système:

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

Lorsque l'IME est fermé, le modificateur imePadding() n'applique aucune marge intérieure, car l'IME n'a pas de hauteur. Étant donné que le modificateur imePadding() n'applique aucune marge intérieure, aucun encart n'est utilisé, et la hauteur de Spacer correspond à celle du bas des barres système.

Lorsque l'IME s'ouvre, il insère une animation pour qu'elle corresponde à la taille de l'IME, et le modificateur imePadding() commence à appliquer une marge intérieure inférieure pour redimensionner LazyColumn à l'ouverture de l'IME. Lorsque le modificateur imePadding() commence à appliquer une marge intérieure inférieure, il commence également à consommer cette quantité d'encarts. Par conséquent, la hauteur de Spacer commence à diminuer, car une partie de l'espacement des barres système a déjà été appliquée par le modificateur imePadding(). Une fois que le modificateur imePadding() applique une marge intérieure inférieure supérieure aux barres système, la hauteur de Spacer est égale à zéro.

Lorsque l'IME se ferme, les modifications se produisent à l'envers: Spacer commence à se développer à partir d'une hauteur de zéro une fois que imePadding() s'applique moins que le bord inférieur des barres système, jusqu'à ce que Spacer corresponde à la hauteur du côté inférieur des barres système une fois l'IME complètement animé.

Figure 2 : Colonne différée bord à bord avec TextField

Ce comportement est obtenu via la communication entre tous les modificateurs windowInsetsPadding et peut être influencé de plusieurs autres manières.

Modifier.consumeWindowInsets(insets: WindowInsets) utilise également les encarts de la même manière que Modifier.windowInsetsPadding, mais il n'applique pas les encarts consommés en tant que marge intérieure. Cette fonctionnalité est utile en association avec les modificateurs de taille d'encart pour indiquer aux frères et sœurs qu'une certaine quantité d'encarts a déjà été utilisée:

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) se comporte de manière très semblable à la version avec un argument WindowInsets, mais nécessite un PaddingValues arbitraire à utiliser. Cela est utile pour informer les enfants lorsque la marge intérieure ou l'espacement est fournie par un autre mécanisme que les modificateurs de marge intérieure d'encart, tels qu'une intercalaire Modifier.padding ordinaire ou à hauteur fixe:

@OptIn(ExperimentalLayoutApi::class)
Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

Si les encarts de fenêtre bruts sont nécessaires sans consommation, utilisez directement les valeurs WindowInsets ou utilisez WindowInsets.asPaddingValues() pour renvoyer une PaddingValues des encarts qui ne sont pas affectés par la consommation. Toutefois, en raison des mises en garde ci-dessous, il est préférable d'utiliser les modificateurs de marge intérieure des encarts de fenêtre et les modificateurs de taille d'encarts de fenêtre dans la mesure du possible.

Phases des encarts et de Jetpack Compose

Compose utilise les API principales d'AndroidX sous-jacentes pour mettre à jour et animer les encarts, qui utilisent les API de la plate-forme sous-jacente qui gèrent les encarts. En raison de ce comportement de la plate-forme, les encarts ont une relation particulière avec les phases de Jetpack Compose.

La valeur des encarts est mise à jour après la phase de composition, mais avant la phase de mise en page. Cela signifie que la lecture de la valeur des encarts dans la composition utilise généralement une valeur d'encarts en retard d'une image. Les modificateurs intégrés décrits sur cette page sont conçus pour retarder l'utilisation des valeurs des encarts jusqu'à la phase de mise en page, ce qui garantit que les valeurs d'encart sont utilisées sur le même frame lors de leur mise à jour.

Animations IME clavier avec WindowInsets

Vous pouvez appliquer Modifier.imeNestedScroll() à un conteneur à défilement pour ouvrir et fermer automatiquement l'IME lorsque vous faites défiler la page vers le bas du conteneur.

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

Animation montrant un élément d&#39;interface utilisateur glissant vers le haut ou vers le bas pour laisser place à un clavier

Figure 1 : Animations IME

Prise en charge des encarts avec les composants Material 3

Pour faciliter leur utilisation, de nombreux composables Material 3 intégrés (androidx.compose.material3) gèrent les encarts selon la façon dont ils sont placés dans votre application selon les spécifications Material.

Gestion des composables à l'intérieur des encarts

Vous trouverez ci-dessous la liste des composants Material qui gèrent automatiquement les encarts.

Barres d'application

Conteneurs

Scaffold

Par défaut, Scaffold fournit des encarts en tant que paramètres paddingValues que vous pouvez utiliser et utiliser. Scaffold n'applique pas les encarts au contenu ; cette responsabilité vous appartient. Par exemple, pour utiliser ces encarts avec un LazyColumn dans un élément 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])
            )
        }
    }
}

Remplacer les encarts par défaut

Vous pouvez modifier le paramètre windowInsets transmis au composable pour configurer son comportement. Ce paramètre peut être un autre type d'encart de fenêtre à appliquer à la place, ou désactivé en transmettant une instance vide : WindowInsets(0, 0, 0, 0).

Par exemple, pour désactiver la gestion des encarts dans LargeTopAppBar, définissez le paramètre windowInsets sur une instance vide:

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

Interopérabilité avec les encarts du système View

Vous devrez peut-être remplacer les encarts par défaut si votre écran contient à la fois des vues et du code Compose dans la même hiérarchie. Dans ce cas, vous devez indiquer clairement lequel doit utiliser les encarts et lequel doit les ignorer.

Par exemple, si votre mise en page de plus haut niveau est une mise en page Android View, vous devez utiliser les encarts dans le système View et les ignorer pour Compose. Si votre mise en page de plus haut niveau est un composable, vous devez utiliser les encarts dans Compose et remplir les composables AndroidView en conséquence.

Par défaut, chaque ComposeView consomme tous les encarts au niveau de consommation WindowInsetsCompat. Pour modifier ce comportement par défaut, définissez ComposeView.consumeWindowInsets sur false.

Ressources

  • Now in Android : une application Android entièrement fonctionnelle, développée entièrement avec Kotlin et Jetpack Compose.