Composants et mises en page Material

Jetpack Compose propose une implémentation de Material Design, un système de conception complet permettant de créer des interfaces numériques. Les composants Material (boutons, fiches, commutateurs, etc.) ainsi que des mises en page telles que Scaffold sont disponibles en tant que fonctions modulables.

Les composants Material sont des blocs de construction interactifs permettant de créer une interface utilisateur. Compose propose une palette de composants prêts à l'emploi. Pour explorer les options disponibles, consultez la documentation de référence de l'API Material pour Compose.

Les composants Material utilisent des valeurs fournies par un MaterialTheme dans votre application :

@Composable
fun MyApp() {
    MaterialTheme {
        // Material Components like Button, Card, Switch, etc.
    }
}

Pour en savoir plus sur la thématisation, consultez les guides sur la conception de systèmes dans Compose.

Emplacements de contenu

Les composants Material qui accueillent du contenu interne (libellés, icônes, etc.) ont tendance à proposer des "emplacements" (des lambdas génériques qui acceptent du contenu composable) ainsi que des constantes publiques, comme la taille et la marge intérieure, pour faire correspondre la mise en page du contenu interne aux spécifications Material.

En voici un exemple avec Button :

Button(
    onClick = { /* ... */ },
    // Uses ButtonDefaults.ContentPadding by default
    contentPadding = PaddingValues(
        start = 20.dp,
        top = 12.dp,
        end = 20.dp,
        bottom = 12.dp
    )
) {
    // Inner content including an icon and a text label
    Icon(
        Icons.Filled.Favorite,
        contentDescription = "Favorite",
        modifier = Modifier.size(ButtonDefaults.IconSize)
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

Figure 1 : À gauche, un Button utilisant l'emplacement content et la marge intérieure par défaut (à gauche). À droite, un Button utilisant l'emplacement content avec un contentPadding personnalisé.

L'élément Button comporte un emplacement générique content avec lambda de fin, qui utilise RowScope pour mettre en page les composables de contenu dans une ligne. Il comporte également un paramètre contentPadding pour appliquer une marge intérieure au contenu interne. Vous pouvez appliquer des valeurs personnalisées ou utiliser les constantes fournies par ButtonDefaults.

Voici un autre exemple, avec ExtendedFloatingActionButton :

ExtendedFloatingActionButton(
    onClick = { /* ... */ },
    icon = {
        Icon(
            Icons.Filled.Favorite,
            contentDescription = "Favorite"
        )
    },
    text = { Text("Like") }
)

Figure 2 : Un ExtendedFloatingActionButton utilisant les emplacements icon et text.

Au lieu d'une expression lambda content générique, ExtendedFloatingActionButton comporte deux emplacements pour une étiquette icon et un libellé text. Bien que chaque emplacement accepte du contenu modulable générique, le composant est catégorique quant à la disposition de ces contenus internes. Il gère la marge intérieure, l'alignement et la taille en interne.

Scaffold

Compose propose des mises en page pratiques pour combiner des composants Material et des modèles d'écran courants. Des composables tels que Scaffold fournissent des emplacements pour divers composants et autres éléments d'écran.

Contenu de l'écran

Scaffold comporte un emplacement générique content avec lambda de fin. L'expression lambda reçoit une instance de PaddingValues applicable à la racine du contenu (par exemple, via Modifier.padding) pour décaler les parties supérieure et inférieure, le cas échéant.

Scaffold(/* ... */) { contentPadding ->
    // Screen content
    Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}

Barres d'application

Scaffold fournit des emplacements pour une barre d'application supérieure ou une barre d'application inférieure. Le placement des composables est géré en interne.

Vous pouvez utiliser l'emplacement topBar avec une TopAppBar :

Scaffold(
    topBar = {
        TopAppBar { /* Top app bar content */ }
    }
) {
    // Screen content
}

Vous pouvez utiliser l'emplacement bottomBar avec une BottomAppBar :

Scaffold(
    bottomBar = {
        BottomAppBar { /* Bottom app bar content */ }
    }
) {
    // Screen content
}

Ces emplacements peuvent être utilisés pour d'autres composants Material Design, tels que BottomNavigation. Vous pouvez également utiliser des composables personnalisés. À titre d'exemple, observez l'écran d'accueil de l'étude Owl.

Boutons d'action flottants

Scaffold fournit un emplacement pour un bouton d'action flottant.

Vous pouvez utiliser l'emplacement floatingActionButton avec un FloatingActionButton :

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    }
) {
    // Screen content
}

Le positionnement inférieur du composable du bouton d'action flottant est géré en interne. Vous pouvez ajuster la position horizontale à l'aide du paramètre floatingActionButtonPosition :

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    // Defaults to FabPosition.End
    floatingActionButtonPosition = FabPosition.Center
) {
    // Screen content
}

Si vous utilisez l'emplacement bottomBar du composable Scaffold, vous pouvez utiliser le paramètre isFloatingActionButtonDocked pour superposer le bouton d'action flottant à la barre d'application inférieure :

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    // Defaults to false
    isFloatingActionButtonDocked = true,
    bottomBar = {
        BottomAppBar { /* Bottom app bar content */ }
    }
) {
    // Screen content
}

Figure 3 : Composable Scaffold utilisant les emplacements floatingActionButton et bottomBar. Le paramètre isFloatingActionButtonDocked est défini sur false dans l'exemple du haut et sur true dans l'exemple du bas.

BottomAppBar accepte les découpes du bouton d'action flottant avec le paramètre cutoutShape, qui accepte n'importe quelle Shape. Nous vous recommandons d'utiliser la même Shape que celle du composant ancré. Par exemple, le paramètre shape par défaut de FloatingActionButton utilise MaterialTheme.shapes.small avec une taille d'angle de 50 % :

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    isFloatingActionButtonDocked = true,
    bottomBar = {
        BottomAppBar(
            // Defaults to null, that is, No cutout
            cutoutShape = MaterialTheme.shapes.small.copy(
                CornerSize(percent = 50)
            )
        ) {
            /* Bottom app bar content */
        }
    }
) {
  // Screen content
}

Figure 4 : Un composable Scaffold avec une BottomAppBar et un FloatingActionButton ancré. La BottomAppBar utilise une cutoutShape personnalisée correspondant à la Shape utilisée par le FloatingActionButton.

Snackbars

Scaffold permet d'afficher des snackbars.

Ce paramètre est fourni via ScaffoldState, qui inclut une propriété SnackbarHostState. Vous pouvez utiliser rememberScaffoldState pour créer une instance de ScaffoldState qui doit être transmise à Scaffold avec le paramètre scaffoldState. SnackbarHostState fournit un accès à la fonction showSnackbar. Cette fonction de suspension nécessite de définir sa CoroutineScope (par exemple, via rememberCoroutineScope) et peut être appelée en réponse à des événements d'interface utilisateur pour afficher une Snackbar dans le Scaffold.

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                scope.launch {
                    scaffoldState.snackbarHostState
                        .showSnackbar("Snackbar")
                }
            }
        )
    }
) {
    // Screen content
}

Vous pouvez ajouter une action facultative et ajuster la durée de Snackbar. La fonction snackbarHostState.showSnackbar accepte des paramètres actionLabel et duration supplémentaires et renvoie un SnackbarResult.

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                scope.launch {
                    val result = scaffoldState.snackbarHostState
                        .showSnackbar(
                            message = "Snackbar",
                            actionLabel = "Action",
                            // Defaults to SnackbarDuration.Short
                            duration = SnackbarDuration.Indefinite
                        )
                    when (result) {
                        SnackbarResult.ActionPerformed -> {
                            /* Handle snackbar action performed */
                        }
                        SnackbarResult.Dismissed -> {
                            /* Handle snackbar dismissed */
                        }
                    }
                }
            }
        )
    }
) {
    // Screen content
}

Vous pouvez fournir une Snackbar personnalisée avec le paramètre snackbarHost. Pour en savoir plus, consultez SnackbarHost API reference docs.

Panneaux

Scaffold fournit un emplacement pour un panneau de navigation modal. L'élément sheet déplaçable et la mise en page du composable sont gérés en interne.

Vous pouvez utiliser l'emplacement drawerContent, qui utilise ColumnScope pour mettre en page les composables de contenu du panneau dans une colonne :

Scaffold(
    drawerContent = {
        Text("Drawer title", modifier = Modifier.padding(16.dp))
        Divider()
        // Drawer items
    }
) {
    // Screen content
}

Scaffold accepte un certain nombre de paramètres de panneau supplémentaires. Par exemple, vous pouvez activer ou désactiver la réaction du panneau aux gestes de type glisser à l'aide du paramètre drawerGesturesEnabled :

Scaffold(
    drawerContent = {
        // Drawer content
    },
    // Defaults to true
    drawerGesturesEnabled = false
) {
    // Screen content
}

Pour programmer l'ouverture et la fermeture du panneau, utilisez ScaffoldState, qui inclut une propriété DrawerState devant être transmise au composable Scaffold avec le paramètre scaffoldState. DrawerState fournit un accès aux fonctions open et close, ainsi qu'aux propriétés liées à l'état actuel du panneau. Ces fonctions de suspension nécessitent de définir leur CoroutineScope (par exemple, via rememberCoroutineScope) et peuvent être appelées en réponse à des événements d'interface utilisateur.

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = {
        // Drawer content
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Open or close drawer") },
            onClick = {
                scope.launch {
                    scaffoldState.drawerState.apply {
                        if (isClosed) open() else close()
                    }
                }
            }
        )
    }
) {
    // Screen content
}

Si vous souhaitez implémenter un panneau de navigation modal sans Scaffold, vous pouvez utiliser le composable ModalDrawer. Il accepte des paramètres de panneau comparables à ceux de Scaffold.

val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
    drawerState = drawerState,
    drawerContent = {
        // Drawer content
    }
) {
    // Screen content
}

Si vous souhaitez implémenter un panneau de navigation en bas de l'écran, vous pouvez utiliser le composable BottomDrawer :

val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
    drawerState = drawerState,
    drawerContent = {
        // Drawer content
    }
) {
    // Screen content
}

Bottom sheets

Si vous souhaitez implémenter une bottom sheet standard, vous pouvez utiliser le composable BottomSheetScaffold. Il accepte des paramètres semblables à ceux de Scaffold, tels que topBar, floatingActionButton et snackbarHost. Il inclut des paramètres supplémentaires qui permettent d'afficher des bottom sheets.

Vous pouvez utiliser l'emplacement sheetContent, qui utilise ColumnScope pour mettre en page les composables de contenu d'une bottom sheet dans une colonne :

BottomSheetScaffold(
    sheetContent = {
        // Sheet content
    }
) {
    // Screen content
}

BottomSheetScaffold accepte un certain nombre de paramètres de bottom sheet supplémentaires. Par exemple, vous pouvez définir la hauteur de l'aperçu de l'élément sheet à l'aide du paramètre sheetPeekHeight. Vous pouvez également activer ou désactiver la réaction du panneau aux gestes de type glisser à l'aide du paramètre sheetGesturesEnabled.

BottomSheetScaffold(
    sheetContent = {
        // Sheet content
    },
    // Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
    sheetPeekHeight = 128.dp,
    // Defaults to true
    sheetGesturesEnabled = false

) {
    // Screen content
}

Pour programmer l'affichage et la réduction de l'élément sheet, utilisez BottomSheetScaffoldState, qui inclut une propriété BottomSheetState. Vous pouvez utiliser rememberBottomSheetScaffoldState pour créer une instance de BottomSheetScaffoldState qui doit être transmise à BottomSheetScaffold avec le paramètre scaffoldState. BottomSheetState fournit un accès aux fonctions expand et collapse, ainsi qu'aux propriétés liées à l'état actuel de l'élément sheet. Ces fonctions de suspension nécessitent de définir leur CoroutineScope (par exemple, via rememberCoroutineScope) et peuvent être appelées en réponse à des événements d'interface utilisateur.

val scaffoldState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
    scaffoldState = scaffoldState,
    sheetContent = {
        // Sheet content
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Expand or collapse sheet") },
            onClick = {
                scope.launch {
                    scaffoldState.bottomSheetState.apply {
                        if (isCollapsed) expand() else collapse()
                    }
                }
            }
        )
    }
) {
    // Screen content
}

Si vous souhaitez implémenter une bottom sheet modale, vous pouvez utiliser le composable ModalBottomSheetLayout :

val sheetState = rememberModalBottomSheetState(
    ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
    sheetState = sheetState,
    sheetContent = {
        // Sheet content
    }
) {
    // Screen content
}

Backdrop

Si vous souhaitez implémenter une toile de fond (backdrop), vous pouvez utiliser le composable BackdropScaffold.

BackdropScaffold(
    appBar = {
        // Top app bar
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    }
)

BackdropScaffold accepte un certain nombre de paramètres de backdrop supplémentaires. Par exemple, vous pouvez définir la hauteur d'aperçu de la toile de fond et la hauteur minimale en inactivité du premier plan avec les paramètres peekHeight et headerHeight. Vous pouvez également activer ou désactiver la réaction du backdrop aux gestes de type glisser à l'aide du paramètre gesturesEnabled.

BackdropScaffold(
    appBar = {
        // Top app bar
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    },
    // Defaults to BackdropScaffoldDefaults.PeekHeight
    peekHeight = 40.dp,
    // Defaults to BackdropScaffoldDefaults.HeaderHeight
    headerHeight = 60.dp,
    // Defaults to true
    gesturesEnabled = false
)

Pour programmer l'affichage et la réduction du backdrop, utilisez BackdropScaffoldState. Vous pouvez utiliser rememberBackdropScaffoldState pour créer une instance de BackdropScaffoldState qui doit être transmise à BackdropScaffold avec le paramètre scaffoldState. BackdropScaffoldState fournit un accès aux fonctions reveal et conceal, ainsi qu'aux propriétés liées à l'état actuel du backdrop. Ces fonctions de suspension nécessitent de définir leur CoroutineScope (par exemple, via rememberCoroutineScope) et peuvent être appelées en réponse à des événements d'interface utilisateur.

val scaffoldState = rememberBackdropScaffoldState(
    BackdropValue.Concealed
)
val scope = rememberCoroutineScope()
BackdropScaffold(
    scaffoldState = scaffoldState,
    appBar = {
        TopAppBar(
            title = { Text("Backdrop") },
            navigationIcon = {
                if (scaffoldState.isConcealed) {
                    IconButton(
                        onClick = {
                            scope.launch { scaffoldState.reveal() }
                        }
                    ) {
                        Icon(
                            Icons.Default.Menu,
                            contentDescription = "Menu"
                        )
                    }
                } else {
                    IconButton(
                        onClick = {
                            scope.launch { scaffoldState.conceal() }
                        }
                    ) {
                        Icon(
                            Icons.Default.Close,
                            contentDescription = "Close"
                        )
                    }
                }
            },
            elevation = 0.dp,
            backgroundColor = Color.Transparent
        )
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    }
)