Jetpack Compose ofrece una implementación de Material Design, un sistema de diseño integral para crear interfaces digitales. Los componentes de Material (botones, tarjetas, interruptores, etc.) y los diseños como Scaffold
están disponibles como funciones que admiten composición.
Los componentes de Material son bloques de compilación interactivos para crear una interfaz de usuario. Compose ofrece varios de estos componentes listos para usar. Para ver cuáles están disponibles, consulta la referencia de la API de Compose Material.
Los componentes de Material usan valores que proporciona un MaterialTheme
en tu app:
@Composable
fun MyApp() {
MaterialTheme {
// Material Components like Button, Card, Switch, etc.
}
}
Para obtener más información sobre los temas, consulta la Guía de temas para Compose.
Espacios de contenido
Los componentes de Material que admiten contenido interno (como etiquetas de texto, íconos, etc.) tienden a ofrecer "espacios" (lambdas genéricas que aceptan contenido que admite composición), además de constantes públicas, como tamaño y relleno, para admitir la disposición del contenido interno de modo que coincida con las especificaciones de Material.
Un ejemplo de esto es 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")
}
Figura 1: Un Button
que usa el espacio content
y el relleno predeterminado (izquierda), y un Button
que utiliza el espacio content
para proporcionar un contentPadding
personalizado (derecha).
Button
tiene un espacio lambda final genérico content
, que usa un RowScope
para diseñar elementos de contenido que admitan composición en una fila. También tiene un parámetro contentPadding
para aplicar relleno al contenido interno. Puedes usar constantes proporcionadas a través de ButtonDefaults
o valores personalizados.
Otro ejemplo es ExtendedFloatingActionButton
:
ExtendedFloatingActionButton(
onClick = { /* ... */ },
icon = {
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
)
},
text = { Text("Like") }
)
Figura 2: Un ExtendedFloatingActionButton
con los espacios icon
y text
.
En lugar de una lambda genérica content
, ExtendedFloatingActionButton
tiene dos espacios para una etiqueta icon
y otra text
. Si bien cada espacio puede incluir contenido genérico que admite composición, el componente considera cómo se disponen esos fragmentos internos. Controla el relleno, la alineación y el tamaño de forma interna.
Scaffold
Compose proporciona diseños convenientes para combinar componentes de Material en patrones de pantalla comunes. Los elementos que admiten composición, como Scaffold
, proporcionan espacios para varios componentes y otros elementos de pantalla.
Contenido de la pantalla
Scaffold
tiene un espacio lambda final genérico: content
. La lambda recibe una instancia de PaddingValues
que se debe aplicar a la raíz del contenido (por ejemplo, a través de Modifier.padding
), para desplazar las barras inferior y superior, si es que las hay.
Scaffold(/* ... */) { contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}
Barras de la app
Scaffold
proporciona espacios para una barra de la app superior o inferior. La ubicación de los elementos que admiten composición se controla de forma interna.
Puedes usar el espacio topBar
y una TopAppBar
:
Scaffold(
topBar = {
TopAppBar { /* Top app bar content */ }
}
) {
// Screen content
}
O bien utilizar el espacio bottomBar
y una BottomAppBar
:
Scaffold(
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
Estos espacios se pueden usar para otros componentes de Material, como BottomNavigation
.
También puedes utilizar elementos que admiten composición personalizados. Por ejemplo, consulta la pantalla de incorporación del ejemplo de Owl.
Botones de acción flotantes
Scaffold
proporciona un espacio para un botón de acción flotante.
Puedes usar el espacio floatingActionButton
y un FloatingActionButton
:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
}
) {
// Screen content
}
La posición inferior del BAF que admite composición se controla de forma interna. Puedes usar el parámetro floatingActionButtonPosition
para ajustar la posición horizontal:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to FabPosition.End
floatingActionButtonPosition = FabPosition.Center
) {
// Screen content
}
Si usas el espacio bottomBar
del elemento que admite composición Scaffold
, puedes usar el parámetro isFloatingActionButtonDocked
para superponer el BAF con la barra de la app inferior:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to false
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
Figura 3: Un Scaffold
que usa los espacios floatingActionButton
y bottomBar
. El parámetro isFloatingActionButtonDocked
se establece en false
(parte superior) y true
(parte inferior).
BottomAppBar
admite cortes de BAF con el parámetro cutoutShape
, que acepta cualquier Shape
. Se recomienda proporcionar la misma Shape
que usa el componente conectado. Por ejemplo, FloatingActionButton
usa MaterialTheme.shapes.small
con un tamaño de esquina del 50% como valor predeterminado para su parámetro shape
:
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
}
Figura 4: Un Scaffold
con una BottomAppBar
y un FloatingActionButton
anclado. BottomAppBar
tiene una cutoutShape
personalizada que coincide con la Shape
que usa el FloatingActionButton
.
Barras de notificaciones
Scaffold
proporciona un medio para mostrar barras de notificaciones.
Esto se proporciona a través de ScaffoldState
, que incluye una propiedad SnackbarHostState
. Puedes usar rememberScaffoldState
para crear una instancia de ScaffoldState
que se debe pasar a Scaffold
con el parámetro scaffoldState
. SnackbarHostState
proporciona acceso a la función showSnackbar
. Esta función de suspensión requiere un CoroutineScope
(por ejemplo, mediante rememberCoroutineScope
), y se la puede llamar en respuesta a eventos de IU para mostrar una Snackbar
en 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
}
Puedes proporcionar una acción opcional y ajustar la duración de Snackbar
.
La función snackbarHostState.showSnackbar
acepta parámetros actionLabel
y duration
adicionales, y muestra 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
}
Puedes proporcionar una Snackbar
personalizada con el parámetro snackbarHost
. Consulta SnackbarHost API reference docs
para obtener más información.
Paneles laterales
Scaffold
proporciona un espacio para un panel lateral de navegación modal. El diseño y la hoja arrastable del elemento que admite composición se controla de forma interna.
Puedes usar el espacio drawerContent
, que usa un ColumnScope
, para disponer en una columna elementos que admiten composición con contenido de paneles laterales:
Scaffold(
drawerContent = {
Text("Drawer title", modifier = Modifier.padding(16.dp))
Divider()
// Drawer items
}
) {
// Screen content
}
Scaffold
acepta una cantidad de parámetros adicionales de panel lateral. Por ejemplo, puedes activar o desactivar si el panel lateral responde a arrastres con el parámetro drawerGesturesEnabled
:
Scaffold(
drawerContent = {
// Drawer content
},
// Defaults to true
drawerGesturesEnabled = false
) {
// Screen content
}
La apertura y el cierre del panel lateral de manera programática se realiza mediante ScaffoldState
, que incluye una propiedad DrawerState
que se debe pasar a Scaffold
con el parámetro scaffoldState
. DrawerState
proporciona acceso a las funciones open
y close
, así como a propiedades relacionadas con el estado actual del panel lateral. Estas funciones de suspensión requieren un CoroutineScope
(por ejemplo, mediante rememberCoroutineScope
), y se lo puede llamar en respuesta a eventos de IU.
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
}
Paneles laterales modales
Si quieres implementar un panel lateral de navegación modal sin un Scaffold
, puedes usar el elemento que admite composición ModalDrawer
. Acepta parámetros de panel lateral similares a Scaffold
.
val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
Si quieres implementar un panel lateral de navegación inferior, puedes usar el elemento que admite composición BottomDrawer
:
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
Hojas inferiores
Si quieres implementar una hoja inferior estándar, puedes usar el elemento que admite composición BottomSheetScaffold
. Acepta parámetros similares a Scaffold
, como topBar
, floatingActionButton
y snackbarHost
. Incluye parámetros adicionales que proporcionan un medio para mostrar hojas inferiores.
Puedes usar el espacio sheetContent
, que usa un ColumnScope
, para disponer en una columna elementos que admiten composición con contenido de hojas:
BottomSheetScaffold(
sheetContent = {
// Sheet content
}
) {
// Screen content
}
BottomSheetScaffold
acepta una cantidad de parámetros adicionales de hoja. Por ejemplo, puedes establecer la altura de la hoja con el parámetro sheetPeekHeight
. También puedes activar o desactivar si la hoja responde a los arrastres con el parámetro sheetGesturesEnabled
.
BottomSheetScaffold(
sheetContent = {
// Sheet content
},
// Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
sheetPeekHeight = 128.dp,
// Defaults to true
sheetGesturesEnabled = false
) {
// Screen content
}
La expansión y contracción de la hoja de forma programática se realiza a través de BottomSheetScaffoldState
, que incluye una propiedad BottomSheetState
. Puedes usar rememberBottomSheetScaffoldState
para crear una instancia de BottomSheetScaffoldState
que se debe pasar a BottomSheetScaffold
con el parámetro scaffoldState
. BottomSheetState
proporciona acceso a las funciones expand
y collapse
, así como a propiedades relacionadas con el estado actual de la hoja. Estas funciones de suspensión requieren un CoroutineScope
(por ejemplo, mediante rememberCoroutineScope
), y se lo puede llamar en respuesta a eventos de IU.
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 quieres implementar una hoja inferior modal, puedes usar el elemento que admite composición ModalBottomSheetLayout
:
val sheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
// Sheet content
}
) {
// Screen content
}
Fondo
Si quieres implementar un fondo, puedes usar el elemento que admite composición BackdropScaffold
.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)
BackdropScaffold
acepta una serie de parámetros de fondo adicionales. Por ejemplo, puedes configurar la altura del aviso de la capa posterior y la altura inactiva mínima de la capa frontal con los parámetros peekHeight
y headerHeight
.
También puedes activar o desactivar si el fondo responde a los arrastres con el parámetro 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
)
Se puede revelar y ocultar el fondo de manera programática mediante BackdropScaffoldState
. Puedes usar rememberBackdropScaffoldState
para crear una instancia de BackdropScaffoldState
que se debe pasar a BackdropScaffold
con el parámetro scaffoldState
. BackdropScaffoldState
proporciona acceso a las funciones reveal
y conceal
, así como a propiedades relacionadas con el estado actual del fondo. Estas funciones de suspensión requieren un CoroutineScope
(por ejemplo, mediante rememberCoroutineScope
), y se lo puede llamar en respuesta a eventos de IU.
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
}
)