O Jetpack Compose oferece uma implementação do Material Design, um sistema de design abrangente
para criar interfaces digitais. Componentes do Material Design (botões, cards, chaves etc.) e layouts como o Scaffold
estão disponíveis como funções que podem ser compostas.
Os componentes do Material Design (link em inglês) são elementos básicos para a criação de uma interface do usuário. O Compose oferece vários componentes prontos para uso. Para ver quais estão disponíveis, confira a referência da API Compose Material.
Os componentes do Material Design usam os valores fornecidos por um MaterialTheme
no
seu app:
@Composable
fun MyApp() {
MaterialTheme {
// Material Components like Button, Card, Switch, etc.
}
}
Para saber mais sobre aplicação de temas, consulte os guias de sistemas de design no Compose.
Slots de conteúdo
Os componentes do Material Design com suporte a conteúdo interno (rótulos de texto, ícones etc.) tendem a oferecer "slots", lambdas genéricas que aceitam conteúdo combinável, bem como constantes públicas, como tamanho e padding, para oferecer suporte à criação do layout do conteúdo interno e corresponder às especificações do Material Design.
Um exemplo disso é o 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. Um Button
usando o slot content
e o padding padrão (à esquerda) e
um Button
usando o slot content
que fornece um contentPadding
personalizado
(à direita).
Button
tem um slot lambda final de content
genérico, que usa um RowScope
para definir o layout
dos conteúdos combináveis em uma linha. Ele também tem um parâmetro contentPadding
para aplicar
o padding ao conteúdo interno. É possível usar constantes fornecidas pelos
ButtonDefaults
ou valores personalizados.
Outro exemplo é o ExtendedFloatingActionButton
:
ExtendedFloatingActionButton(
onClick = { /* ... */ },
icon = {
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
)
},
text = { Text("Like") }
)
Figura 2. Um ExtendedFloatingActionButton
usando os slots icon
e text
.
Em vez de um lambda final de content
genérico, ExtendedFloatingActionButton
tem dois
slots para um icon
e um rótulo de text
. Embora cada slot ofereça suporte para conteúdo genérico
que pode ser composto, o componente é rigoroso sobre como esse
conteúdo interno é exibido. Ele processa o padding, o alinhamento e o tamanho
internamente.
Scaffold
O Compose oferece layouts convenientes para combinar componentes do Material Design em
padrões de tela comuns. Funções que podem ser compostas, como Scaffold
,
fornecem slots para vários componentes e outros elementos de tela.
Conteúdo da tela
Scaffold
tem um slot lambda final de content
genérico. A lambda recebe uma
instância de PaddingValues
que
precisa ser aplicada à raiz do conteúdo, por exemplo, via Modifier.padding
,
para compensar as partes de cima e de baixo das barras, se elas existirem.
Scaffold(/* ... */) { contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}
Barras de apps
Scaffold
fornece slots para uma barra de apps na parte superior ou na parte inferior. A posição das
funções que podem ser compostas é processada internamente.
É possível usar o slot topBar
e uma TopAppBar
:
Scaffold(
topBar = {
TopAppBar { /* Top app bar content */ }
}
) {
// Screen content
}
É possível usar o slot bottomBar
e uma BottomAppBar
:
Scaffold(
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
Esses slots podem ser usados para outros componentes do Material Design, como a BottomNavigation
.
Também é possível usar elementos de composição personalizados. Para ver um exemplo, consulte a
tela de integração
da amostra do Owl.
Botões de ação flutuantes
Scaffold
fornece um slot para um botão de ação flutuante.
É possível usar o slot floatingActionButton
e um FloatingActionButton
:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
}
) {
// Screen content
}
A posição inferior do FAB que pode ser composto é processada internamente. É possível usar
o parâmetro floatingActionButtonPosition
para ajustar a posição
horizontal:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to FabPosition.End
floatingActionButtonPosition = FabPosition.Center
) {
// Screen content
}
Se você usa o slot bottomBar
do elemento de composição Scaffold
, pode usar o
parâmetro isFloatingActionButtonDocked
para sobrepor o FAB com a barra de apps
inferior:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to false
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
Figura 3. Um Scaffold
usando os slots floatingActionButton
e
bottomBar
. O parâmetro isFloatingActionButtonDocked
está definido como
false
(parte de cima) e true
(parte de baixo).
BottomAppBar
oferece suporte a cortes no FAB com o parâmetro cutoutShape
, que
aceita qualquer Shape
. É recomendável
fornecer o mesmo Shape
usado pelo componente fixado. Por exemplo,
FloatingActionButton
usa MaterialTheme.shapes.small
com um tamanho de
canto de 50% como valor padrão para o 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. Um Scaffold
com uma BottomAppBar
e um FloatingActionButton
na base. A BottomAppBar
tem uma cutoutShape
personalizada que
corresponde ao Shape
usado pelo FloatingActionButton
.
Snackbars
O Scaffold
fornece uma maneira de exibir snackbars.
Isso é fornecido pelo ScaffoldState
, que inclui uma
propriedade
SnackbarHostState
. É possível usar
rememberScaffoldState
para criar uma instância do ScaffoldState
que precisa ser transmitida para o Scaffold
com o parâmetro scaffoldState
. SnackbarHostState
fornece acesso à
função
showSnackbar
. Essa função de suspensão exige um CoroutineScope
, por exemplo,
usando
rememberCoroutineScope
,
e pode ser chamada em resposta a eventos da IU para mostrar uma
Snackbar
em um 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
}
Você pode fornecer uma ação opcional e ajustar a duração da Snackbar
.
A função snackbarHostState.showSnackbar
aceita outros parâmetros actionLabel
e duration
e retorna um 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
}
É possível fornecer uma Snackbar
personalizada com o parâmetro snackbarHost
. Consulte
SnackbarHost API reference docs
para
ver mais informações.
Gavetas
Scaffold
fornece um slot para uma gaveta modal de navegação. A página e o layout arrastáveis
do elemento que pode ser composto são processados internamente.
Você pode usar o slot drawerContent
, que usa um ColumnScope
para definir o layout
dos elementos de composição de conteúdo de gaveta em uma coluna:
Scaffold(
drawerContent = {
Text("Drawer title", modifier = Modifier.padding(16.dp))
Divider()
// Drawer items
}
) {
// Screen content
}
Scaffold
aceita vários outros parâmetros de gaveta. Por exemplo, é
possível ativar ou desativar a resposta da gaveta às ações de arrastar com o
parâmetro drawerGesturesEnabled
:
Scaffold(
drawerContent = {
// Drawer content
},
// Defaults to true
drawerGesturesEnabled = false
) {
// Screen content
}
A abertura e o fechamento da gaveta são feitos de forma programática pelo ScaffoldState
, que inclui uma propriedade do
DrawerState
que precisa ser
transmitida para o Scaffold
com o parâmetro scaffoldState
. DrawerState
fornece acesso às funções open
e close
, além
de propriedades relacionadas ao estado atual da gaveta. Essas funções
de suspensão exigem um CoroutineScope
, por exemplo, usando
rememberCoroutineScope
,
e podem ser chamadas em resposta a eventos da 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
}
Gavetas modais
Caso você queira implementar uma gaveta de navegação modal sem um Scaffold
, use
o elemento de composição ModalDrawer
. Ela aceita parâmetros de gaveta parecidos com o Scaffold
.
val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
Se você quiser implementar uma gaveta de navegação inferior, use a
BottomDrawer
que pode ser composta:
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
Páginas inferiores
Caso você queira implementar uma página inferior padrão, use
o elemento BottomSheetScaffold
que pode ser composto. Ele aceita parâmetros parecidos para Scaffold
, como topBar
,
floatingActionButton
e snackbarHost
, além de incluir outros parâmetros
que fornecem uma maneira de exibir as páginas inferiores.
Você pode usar o slot sheetContent
, que usa um ColumnScope
para definir o layout dos conteúdos combináveis da página em uma coluna:
BottomSheetScaffold(
sheetContent = {
// Sheet content
}
) {
// Screen content
}
BottomSheetScaffold
aceita vários outros parâmetros de página. Por
exemplo, você pode definir a altura máxima da página com o parâmetro
sheetPeekHeight
. Também pode ativar ou desativar a resposta da gaveta às ações de arrastar com
o parâmetro sheetGesturesEnabled
.
BottomSheetScaffold(
sheetContent = {
// Sheet content
},
// Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
sheetPeekHeight = 128.dp,
// Defaults to true
sheetGesturesEnabled = false
) {
// Screen content
}
A expansão e o recolhimento da página de forma programática são feitos pelo
BottomSheetScaffoldState
, que
inclui uma propriedade BottomSheetState
. É
possível usar rememberBottomSheetScaffoldState
para criar uma instância do BottomSheetScaffoldState
que precisa ser transmitida para o
BottomSheetScaffold
com o parâmetro scaffoldState
. BottomSheetState
fornece acesso às funções expand
e
collapse
,
além de propriedades relacionadas ao estado da página. Essas funções
de suspensão exigem um CoroutineScope
, por exemplo, usando
rememberCoroutineScope
,
e podem ser chamadas em resposta a eventos da 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
}
Caso você queira implementar uma página inferior modal, use o elemento ModalBottomSheetLayout
que pode ser composto:
val sheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
// Sheet content
}
) {
// Screen content
}
Pano de fundo
Caso você queira implementar um pano de fundo, é possível usar o elemento que pode ser composto
BackdropScaffold
.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)
BackdropScaffold
aceita vários outros parâmetros de pano de fundo. Por
exemplo, você pode definir a altura máxima da camada de fundo e a altura mínima
inativa da camada em primeiro plano com os parâmetros peekHeight
e headerHeight
.
Também é possível ativar ou desativar a resposta do pano de fundo às ações de arrastar com o
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
)
A revelação e a ocultação de panos de fundo de maneira programática são feitas pelo
BackdropScaffoldState
. É possível
usar rememberBackdropScaffoldState
para criar uma instância do BackdropScaffoldState
que precisa ser transmitida para o
BackdropScaffold
com o parâmetro scaffoldState
. BackdropScaffoldState
fornece acesso às funções reveal
e
conceal
,
além de propriedades relacionadas ao estado do pano de fundo. Essas
funções de suspensão exigem um CoroutineScope
, por exemplo, usando
rememberCoroutineScope
,
e podem ser chamadas em resposta a eventos da 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
}
)