Prérequis
- Le projet DOIT utiliser Android Jetpack Compose.
- Le projet DOIT cibler le SDK 35 ou supérieur. Si le SDK est inférieur à 35, augmentez-le à 35.
Étape 1 : planification
- Localisez et analysez toutes les classes Activity pour détecter celles qui ont déjà une prise en charge edge-to-edge. Pour chaque Activity sans edge-to-edge, planifiez de la rendre edge-to-edge.
- Dans chaque Activity, localisez et analysez tous les composants de liste et FAB pour détecter ceux qui ont déjà une prise en charge edge-to-edge. Pour chaque composant sans edge-to-edge, planifiez de le rendre edge-to-edge.
- Dans chaque Activity, recherchez les
TextField,OutlinedTextFieldouBasicTextField. Si vous en trouvez, vous DEVEZ vérifier que l'IME ne masque pas le champ de saisie en suivant la section IME de cette compétence.
Étape 2 : ajouter la prise en charge edge-to-edge
- Ajoutez
enableEdgeToEdgeavantsetContentdansonCreatepour chaque Activity qui n'appelle pas déjàenableEdgeToEdge. - Ajoutez
android:windowSoftInputMode="adjustResize"dansAndroidManifest.xmlpour toutes les Activities qui utilisent un clavier logiciel.
Étape 3 : appliquer les insets
-
L'application DOIT appliquer les insets système, ou aligner le contenu sur les règles, afin que les éléments critiques de l'interface restent cliquables. Choisissez une seule méthode pour éviter un double espacement :
- PRÉFÉRÉ : Lorsque disponible, utilisez
Scaffoldet passezPaddingValuesau lambda de contenu.
Scaffold { innerPadding -> // innerPadding prend en compte les barres système et les composants du Scaffold LazyColumn( modifier = Modifier .fillMaxSize() .consumeWindowInsets(innerPadding), contentPadding = innerPadding ) { /* Contenu */ } }-
PRÉFÉRÉ : Lorsque disponible, utilisez la gestion automatique des insets ou les modificateurs de remplissage dans les composants Material.
- Les composants Material 3 gèrent les zones de sécurité pour leurs propres composants, notamment :
TopAppBarSmallTopAppBarCenterAlignedTopAppBarMediumTopAppBarLargeTopAppBarBottomAppBarModalDrawerSheetDismissibleDrawerSheetPermanentDrawerSheetModalBottomSheetNavigationBarNavigationRail
- Pour les composants Material 2, utilisez le paramètre
windowInsetspour appliquer manuellement les insets pourBottomAppBar,TopAppBaretBottomNavigation. N'APPLIQUEZ PAS de remplissage au conteneur parent ; passez plutôt les insets directement au composant App Bar. Appliquer un remplissage au conteneur parent empêche le fond de l'App Bar de s'étendre dans la zone de la barre système. Par exemple, pourTopAppBar, choisissez une seule des options suivantes :- PRÉFÉRÉ :
TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets) TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
- PRÉFÉRÉ :
- Les composants Material 3 gèrent les zones de sécurité pour leurs propres composants, notamment :
-
Pour les composants en dehors d'un Scaffold, utilisez des modificateurs de remplissage, tels que
Modifier.safeDrawingPadding()ouModifier.windowInsetsPadding(WindowInsets.safeDrawing).Box( modifier = Modifier .fillMaxSize() .safeDrawingPadding() ) { Button( onClick = {}, modifier = Modifier.align(Alignment.BottomCenter) ) { Text("Connexion") } } -
Pour les composants profondément imbriqués avec un espacement excessif, utilisez
WindowInsetsRulers(par exempleModifier.fitInside(WindowInsetsRulers.SafeDrawing.current)). Voir la section IME pour un exemple de code. -
Lorsque vous avez besoin qu'un élément (par exemple un en-tête personnalisé ou un effet de scrim décoratif) ait les mêmes dimensions qu'une barre système, utilisez des modificateurs de taille d'inset (par exemple
Modifier.windowInsetsTopHeight(WindowInsets.systemBars)). Voir la section Listes pour un exemple de code.
- PRÉFÉRÉ : Lorsque disponible, utilisez
Scaffolds adaptatifs
NavigationSuiteScaffoldgère les zones de sécurité pour ses propres composants, commeNavigationRailouNavigationBar. Cependant, les scaffolds adaptatifs (par exempleNavigationSuiteScaffold,ListDetailPaneScaffold) ne propagent pasPaddingValuesà leur contenu interne. Vous DEVEZ appliquer les insets aux écrans ou composants individuels (par exemplecontentPaddingd'une liste ou remplissage d'un FAB) comme décrit dans Étape 3. N'APPLIQUEZ PASsafeDrawingPaddingou des modificateurs similaires au parentNavigationSuiteScaffold. Cela couperait le contenu et empêcherait un affichage edge-to-edge.
IME
- Pour chaque Activity avec un clavier logiciel, vérifiez que
android:windowSoftInputMode="adjustResize"est défini dansAndroidManifest.xml. N'utilisez PASSOFT_INPUT_ADJUST_RESIZEcar il est déprécié. Ensuite, conservez le focus sur le champ de saisie. Choisissez une option :-
- PRÉFÉRÉ : Ajoutez
Modifier.fitInside(WindowInsetsRulers.Ime.current)au conteneur de contenu. Ceci est préféré àimePadding()car cela réduit les à-coups et le remplissage supplémentaire causé par l'oubli de consommer les insets en amont dans la hiérarchie.
- PRÉFÉRÉ : Ajoutez
-
- Ajoutez
imePaddingau conteneur de contenu. Le modificateur de remplissage DOIT être placé avantModifier.verticalScroll(). N'utilisez PASModifier.imePadding()si le parent prend déjà en compte l'IME aveccontentWindowInsets(par exemplecontentWindowInsets = WindowInsets.safeDrawing). Cela provoquerait un double remplissage.
- Ajoutez
-
Motifs de code IME avec Scaffold
CORRECT
CORRECT car contentWindowInsets contient les insets IME, qui sont transmis au lambda de contenu sous forme de innerPadding.
// CORRECT
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.verticalScroll(rememberScrollState())
) { /* Contenu */ }
}
CORRECT car fitInside ajuste le contenu aux insets IME indépendamment de contentWindowInsets.
// CORRECT
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.fitInside(WindowInsetsRulers.Ime.current)
.verticalScroll(rememberScrollState())
) { /* Contenu */ }
}
CORRECT car le contentWindowInsets par défaut ne contient pas les insets IME, et imePadding() applique les insets IME :
// CORRECT
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.imePadding()
.verticalScroll(rememberScrollState())
) { /* Contenu */ }
}
INCORRECT
INCORRECT car il y aura un remplissage excessif lorsque l'IME s'ouvre. Les insets IME sont appliqués deux fois, une fois avec innerPadding, qui contient les insets IME des valeurs contentWindowInsets transmises, et une fois avec imePadding :
// INCORRECT
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.imePadding()
.verticalScroll(rememberScrollState())
) { /* Contenu */ }
}
INCORRECT car l'IME masquera le contenu. Le contentWindowInsets par défaut de Scaffold ne contient PAS les insets IME.
// INCORRECT
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) { /* Contenu */ }
}
Motifs de code IME sans Scaffold
CORRECT
Les exemples de code suivants NE PROVOQUERONT PAS de remplissage excessif.
// CORRECT
Box(
// Insets consommés
modifier = Modifier.safeDrawingPadding() // ou imePadding(), safeContentPadding(), safeGesturesPadding()
) {
Column(
modifier = Modifier.imePadding()
) { /* Contenu */ }
}
// CORRECT
Box(
// Insets consommés
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // ou WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures
) {
Column(
modifier = Modifier.imePadding()
) { /* Contenu */ }
}
// CORRECT
Box(
// Insets non consommés, mais sans importance à cause de fitInside
modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // ou WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
Column(
modifier = Modifier
.fillMaxSize()
.fitInside(WindowInsetsRulers.Ime.current)
) { /* Contenu */ }
}
INCORRECT
L'exemple de code suivant PROVOQUERA un remplissage excessif car les insets IME sont appliqués deux fois :
// INCORRECT
Box(
// Insets non consommés
modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // ou WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
Column(
modifier = Modifier.imePadding()
) { /* Contenu */ }
}
Contraste de la barre de navigation et icônes des barres système
-
Si l'Activity utilise
enableEdgeToEdgedepuisWindowCompat, vous DEVEZ définirisAppearanceLightNavigationBarsetisAppearanceLightStatusBarssur l'inverse du thème de l'appareil pour les applications prenant en charge les thèmes clair et sombre, afin que les icônes de la barre système soient lisibles. Il est recommandé de le faire dans votre fichier de thème. NE le faites PAS si les Activities utilisentenableEdgeToEdgedepuisComponentActivitycar il gère automatiquement les couleurs des icônes.// À utiliser uniquement si vous appelez `enableEdgeToEdge` depuis `WindowCompat`. // Appliquez à votre fichier de thème. @Composable fun MyTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { val view = LocalView.current if (!view.isInEditMode) { SideEffect { val window = (view.context as? Activity)?.window ?: return@SideEffect val controller = WindowCompat.getInsetsController(window, view) // Icônes sombres pour le mode clair (!darkTheme), icônes claires pour le mode sombre controller.isAppearanceLightStatusBars = !darkTheme controller.isAppearanceLightNavigationBars = !darkTheme } } MaterialTheme(content = content) } -
Si un écran utilise un
Scaffoldou unNavigationSuiteScaffoldavec une barre inférieure (par exempleBottomAppBar,NavigationBar), définissezwindow.isNavigationBarContrastEnforced = falsedans l'Activity correspondante pour le SDK 29+. Cela empêche le système d'ajouter un fond translucide à la barre de navigation, garantissant ainsi que les couleurs de votre barre inférieure s'étendent jusqu'en bas de l'écran.
Listes
- Appliquez le remplissage des insets (comme
innerPaddingdeScaffold) au paramètrecontentPaddingdes composants défilables (par exempleLazyColumn,LazyRow). NE l'appliquez PAS commeModifier.padding()au conteneur parent de la liste, car cela couperait le contenu et l'empêcherait de défiler derrière les barres système. - Créez un composable translucide recouvrant la barre système pour que les icônes restent lisibles.
class SystemBarProtectionSnippets : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// enableEdgeToEdge définit window.isNavigationBarContrastEnforced = true
// ce qui sert à ajouter un voile translucide pour la navigation à trois boutons
enableEdgeToEdge()
setContent {
MyTheme {
// Contenu principal
MyContent()
// Après avoir dessiné le contenu principal, dessinez la protection de la barre d'état
StatusBarProtection()
}
}
}
}
@Composable
private fun StatusBarProtection(
color: Color = MaterialTheme.colorScheme.surfaceContainer,
) {
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(
with(LocalDensity.current) {
(WindowInsets.statusBars.getTop(this) * 1.2f).toDp()
}
)
.background(
brush = Brush.verticalGradient(
colors = listOf(
color.copy(alpha = 1f),
color.copy(alpha = 0.8f),
Color.Transparent
)
)
)
)
}
Boîtes de dialogue
Si les deux conditions suivantes sont vraies, alors la boîte de dialogue est en plein écran et doit être rendue edge-to-edge :
- Les
DialogPropertiescontiennentusePlatformDefaultWidth = false. - La boîte de dialogue appelle
Modifier.fillMaxSize().
Pour rendre une boîte de dialogue plein écran edge-to-edge, définissez decorFitsSystemWindows = false dans les DialogProperties.
Dialog(
onDismissRequest = { /* Gérer la fermeture */ },
properties = DialogProperties(
// 1. Permet à la boîte de dialogue de s'étendre sur toute la largeur de l'écran
usePlatformDefaultWidth = false,
// 2. Permet à la boîte de dialogue de se dessiner derrière les barres de navigation et d'état
decorFitsSystemWindows = false
)
) { /* Contenu */ }
Liste de vérification
- Chaque
Activityappelle-t-elleenableEdgeToEdge()? -
adjustResizeest-il défini dansAndroidManifest.xml? - Chaque
TextField,OutlinedTextFieldouBasicTextFielda-t-il un parent avecimePadding(),fitInside,Modifier.safeDrawingPadding(),Modifier.safeContentPadding(),Modifier.safeGesturesPadding(), oucontentWindowInsetsdéfini surWindowInsets.safeDrawingouWindowInsets.ime? - Le premier et le dernier élément de la liste sont-ils éloignés des barres système en passant les insets à
contentPadding? - Les FAB sont-ils dessinés au-dessus des barres de navigation, soit en étant à l'intérieur d'un Scaffold, soit en appliquant
Modifier.safeDrawingPadding()? - Le projet se compile-t-il ? Exécutez
./gradlew buildpour vous en assurer.