Conceitos básicos de layout do Compose

O Jetpack Compose facilita muito o processo de projetar e criar a IU do app. O Compose transforma o estado em elementos da IU usando:

  1. Composição de elementos
  2. Layout dos elementos
  3. Desenho de elementos

Compose transforma estado na IU usando composição, layout e desenho

Este documento se concentra no layout dos elementos, explicando alguns dos recursos básicos oferecidos pelo Compose para ajudar no layout dos elementos da IU.

Metas de layout no Compose

A implementação do sistema de layout do Jetpack Compose tem duas metas principais:

Noções básicas das funções combináveis

Funções combináveis são o elemento básico fundamental do Compose. Uma função que pode ser composta é uma função que emite uma Unit de descrição de alguma parte da IU. A função recebe alguma entrada e gera o que será exibido na tela. Para ver mais informações sobre funções que podem ser compostas, consulte a documentação Modelo mental do Compose.

Uma função combinável pode emitir vários elementos da interface. No entanto, se você não fornecer orientações sobre como eles devem ser organizados, o Compose poderá organizar os elementos de uma maneira que você não quer. Por exemplo, este código gera dois elementos de texto:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Sem orientação sobre como você quer organizá-los, o Compose colocará os elementos de texto um sobre o outro, o que os deixará ilegíveis:

Dois elementos de texto desenhados um sobre o outro, o que torna o texto ilegível

O Compose fornece uma coleção de layouts prontos para uso que ajudam você a organizar seus elementos da IU e facilita a definição dos seus layouts mais especializados.

Componentes de layout padrão

Em muitos casos, você pode simplesmente usar os elementos de layout padrão do Compose.

Use Column para colocar itens na tela verticalmente.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Dois elementos de texto organizados em um layout de coluna, de modo que o texto seja legível

Da mesma forma, use Row para colocar itens na tela horizontalmente. Tanto Column quanto Row são compatíveis com a configuração de alinhamento dos elementos que eles contêm.

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Mostra um layout mais complexo, com uma pequena imagem ao lado de uma coluna de elementos de texto

Use Box para colocar elementos uns sobre os outros. A Box também oferece suporte à configuração de alinhamento específico dos elementos que ela contém.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Mostra dois elementos empilhados um sobre o outro

Geralmente, esses elementos de criação são tudo o que você precisa. Você pode criar sua própria função que pode ser composta para combinar esses layouts em um mais elaborado, que seja adequado ao seu app.

Compara três layouts simples que podem ser compostos: coluna, linha e caixa

Para definir a posição dos filhos em uma Row, defina os argumentos horizontalArrangement e verticalAlignment. Para uma Column, defina os argumentos verticalArrangement e horizontalAlignment:

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Os itens estão alinhados à direita

Modelo de layout

No modelo de layout, a árvore da interface é disposta em uma única transmissão. Primeiro, cada nó precisa medir a si mesmo, e depois medir todos os filhos recursivamente, transmitindo as restrições de tamanho para baixo aos filhos. Depois, os nós de folhas são dimensionados e posicionados, com os tamanhos resolvidos e as instruções de posicionamento retornados para a árvore.

Em resumo, os pais medem antes dos filhos, mas são dimensionados e colocados depois dos filhos.

Considere a seguinte função SearchResult.

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Essa função produz a seguinte árvore de IU.

SearchResult
  Row
    Image
    Column
      Text
      Text

No exemplo do SearchResult, o layout da árvore de IU segue esta ordem:

  1. É solicitado que o nó raiz Row faça a medição.
  2. O nó raiz Row pede para o primeiro filho, Image, medir.
  3. O Image é um nó de folha (ou seja, não tem filhos). Portanto, ele informa um tamanho e retorna as instruções de posicionamento.
  4. O nó raiz Row pede para o segundo filho, Column, medir.
  5. O nó Column pede ao primeiro filho Text para medir.
  6. O primeiro nó Text é um nó de folha. Portanto, ele informa um tamanho e retorna as instruções de posicionamento.
  7. O nó Column pede ao segundo filho Text para medir.
  8. O segundo nó Text é um nó de folha. Portanto, ele informa um tamanho e retorna as instruções de posicionamento.
  9. Agora que o nó Column mediu, dimensionou e posicionou os filhos, ele pode determinar o próprio tamanho e posicionamento.
  10. Agora que o nó raiz Row mediu, dimensionou e posicionou os filhos, ele pode determinar o próprio tamanho e posicionamento.

Ordem para medir, dimensionar e posicionar na árvore de IU de resultados da pesquisa

Desempenho

O Compose alcança um alto desempenho medindo os filhos apenas uma vez. A medição de passagem única é boa para o desempenho, possibilitando que o Compose processe árvores de IU profundas com eficiência. Se um elemento de layout medisse o filho duas vezes e esse filho medisse cada filho dele duas vezes, e assim por diante, uma única tentativa de dispor uma IU inteira tomaria muito trabalho, dificultando a manutenção do bom desempenho do app.

Caso seu layout precise de várias medidas por algum motivo, o Compose oferece um sistema especial para isso, as medições intrínsecas. Leia mais sobre esse recurso em Medidas intrínsecas em layouts do Compose.

Como a medição e a colocação são subfases distintas da transmissão de layout, qualquer mudança que afete apenas a colocação de itens, e não a medição, pode ser executada separadamente.

Como usar modificadores em layouts

Como discutido em Modificadores do Compose, você pode usar modificadores para decorar ou aumentar as funções que podem ser compostas. Modificadores são essenciais para personalizar o layout. Por exemplo, encadeamos vários modificadores para personalizar o ArtistCard:

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

Um layout ainda mais complexo, usando modificadores para mudar a forma como os elementos gráficos são organizados e quais áreas respondem às entradas do usuário

Observe diferentes funções de modificador sendo usadas juntas no código acima.

  • clickable gera uma reação que pode ser composta à entrada do usuário e exibe uma ondulação.
  • padding cria um espaço ao redor de um elemento.
  • fillMaxWidth faz a função que pode ser composta preencher a largura máxima atribuída a ela pelo elemento pai.
  • size() especifica a largura e a altura preferenciais de um elemento.

Layouts roláveis

Para saber mais sobre layouts roláveis, consulte a documentação sobre gestos no Compose.

Para listas comuns e lentas, consulte a documentação sobre listas do Compose.

Layouts responsivos

Um layout precisa ser projetado pensando em diferentes orientações de tela e tamanhos. O Compose oferece alguns mecanismos prontos para facilitar a adaptação dos layouts de funções que podem ser compostas a diferentes configurações de tela.

Restrições

Para saber quais são as restrições provenientes do pai e projetar o layout de acordo com isso, é necessário usar BoxWithConstraints. As restrições de medidas podem ser encontradas no escopo do lambda do conteúdo. Essas restrições de medidas podem ser usadas para compor diferentes layouts para diferentes configurações de tela:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Layouts baseados em slot

O Compose oferece uma grande variedade de funções baseadas no Material Design com a dependência androidx.compose.material:material (incluída ao criar o projeto do Compose no Android Studio) para facilitar a criação da IUs. Elementos como Drawer, FloatingActionButton e TopAppBar são todos fornecidos pelo Compose.

Componentes do Material Design usam muito as APIs de slot, um padrão que foi introduzido no Compose para oferecer uma camada de personalização, além dos elementos que podem ser compostos. Essa abordagem faz com que os componentes sejam mais flexíveis, porque eles aceitam um elemento filho que pode se configurar. Portanto, não é mais necessário expor todos os parâmetros de configuração do filho. Os slots deixam um espaço vazio na IU, que o desenvolvedor pode preencher como quiser. Por exemplo, estes são os slots que podem ser personalizados em um TopAppBar:

Diagrama mostrando os slots disponíveis em uma barra de apps com componentes do Material Design.

Os elementos que podem ser compostos geralmente usam um lambda que pode ser composto content ( content: @Composable () -> Unit). As APIs de slot expõem vários parâmetros content para usos específicos. Por exemplo, TopAppBar permite que você forneça o conteúdo para title, navigationIcon e actions.

Por exemplo, Scaffold permite implementar uma IU com a estrutura básica de layout do Material Design. Scaffold fornece slots para os componentes de alto nível mais comuns do Material Design, como TopAppBar, BottomAppBar, FloatingActionButton, e Drawer. Usando Scaffold, é fácil garantir que esses componentes estejam posicionados corretamente e funcionem bem juntos.

App de amostra JetNews, que usa o Scaffold para posicionar vários elementos.

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}