Modificadores do Compose

Os modificadores permitem decorar ou aumentar as funções que podem ser compostas. Com os modificadores, é possível fazer o seguinte:

  • Mudar o tamanho, o layout, o comportamento e a aparência do elemento
  • Adicionar informações, como rótulos de acessibilidade
  • Processar a entrada do usuário
  • Adicionar interações de nível superior, como tornar um elemento clicável, rolável, arrastável ou redimensionável

Modificadores são objetos Kotlin padrão. Crie um modificador chamando uma das funções de classe Modifier.

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Duas linhas de texto em um plano de fundo colorido, com padding ao redor do texto.

É possível encadear essas funções para fazer a composição delas:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

O plano de fundo colorido por trás do texto agora se estende para preencher toda a largura do dispositivo.

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

  • 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.

É uma prática recomendada que todos os elementos combináveis aceitem um parâmetro modifier e transmitam esse modificador ao primeiro filho que emite a interface. Isso torna seu código mais reutilizável e deixa o comportamento dele mais previsível e intuitivo. Para mais informações, consulte as diretrizes da API Compose: Os elementos aceitam e respeitam um parâmetro modificador.

A ordem dos modificadores é importante

A ordem das funções modificadoras é importante. Como cada função realiza mudanças no Modifier retornado pela função anterior, a sequência afeta o resultado final. Veja um exemplo disso:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Toda a área, incluindo o preenchimento ao redor das bordas, responde aos cliques

No código acima, a área inteira é clicável, incluindo o padding ao redor dela, porque o modificador padding foi aplicado depois do modificador clickable. Se a ordem dos modificadores for invertida, o espaço adicionado por padding não reagirá à entrada do usuário:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

O preenchimento ao redor da borda do layout não responde mais aos cliques

Modificadores integrados

O Jetpack Compose oferece uma lista de modificadores integrados para ajudar você a decorar ou aumentar uma função que pode ser composta. Veja alguns modificadores comuns que você vai usar para ajustar seus layouts.

padding e size

Por padrão, os layouts fornecidos no Compose unem os filhos. No entanto, é possível definir um tamanho usando o modificador size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

O tamanho especificado pode não ser respeitado caso ele não atenda às restrições provenientes do pai do layout. Caso você precise que o tamanho do elemento combinável seja corrigido, independente das restrições de entrada, use o modificador requiredSize:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

A imagem filha é maior do que as restrições do pai

Nesse exemplo, mesmo com a height do elemento pai definida como 100.dp, a altura da Image vai ser 150.dp, já que o modificador requiredSize tem precedência.

Caso você queira que um layout filho preencha toda a altura disponibilizada pelo pai, adicione o modificador fillMaxHeight. O Compose também oferece fillMaxSize e fillMaxWidth:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

A altura da imagem é tão grande quanto a do pai

Para adicionar padding ao redor de um elemento, defina um modificador padding.

Caso queira adicionar padding acima da linha de base do texto, de modo a estabelecer uma distância específica do topo do layout até a linha de base, use o modificador paddingFromBaseline:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Texto com padding na parte superior

Offset

Para posicionar um layout em relação à posição original, adicione o modificador offset e defina o deslocamento no eixo x e y. Os deslocamentos podem ser positivos e não positivos. A diferença entre padding e offset é que adicionar um offset a uma função não muda as medidas dela:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Texto deslocado para o lado direito do contêiner pai

O modificador offset é aplicado horizontalmente, de acordo com a direção do layout. Em um contexto de sentido da esquerda para a direita, um offset positivo desloca o elemento para a direita. Já em um contexto de sentido da direita para esquerda, o elemento é deslocado para a esquerda. Caso seja necessário definir um deslocamento sem considerar a direção do layout, analise o modificador absoluteOffset, em que um valor de deslocamento positivo sempre desloca o elemento para a direita.

O modificador offset fornece duas sobrecargas: offset, que usa os deslocamentos como parâmetros, e offset, que usa uma lambda. Para informações mais detalhadas sobre quando usar cada uma delas e como otimizar o desempenho, leia a seção Desempenho do Compose: adiar leituras pelo maior tempo possível.

Segurança de escopo no Compose

No Compose, há modificadores que só podem ser usados quando aplicados a filhos de determinados elementos combináveis. Ele aplica essa segurança usando escopos personalizados.

Por exemplo, se você quiser deixar um filho do tamanho do elemento Box pai sem afetar o tamanho do Box, use o modificador matchParentSize. O matchParentSize está disponível apenas no BoxScope. Portanto, ele só pode ser usado em um filho dentro de um Box pai.

A segurança de escopo impede que você adicione modificadores que não funcionam em outros escopos e elementos combináveis, além de economizar tempo de tentativa e erro.

Os modificadores com escopo informam o pai sobre algumas informações que ele precisa saber sobre o elemento filho. Eles também costumam ser chamados de modificadores de dados pai. Os componentes internos são diferentes dos modificadores de uso geral, mas do ponto de vista do uso, essas diferenças não importam.

matchParentSize em Box

Como mencionado acima, se você quiser que um layout filho tenha o mesmo tamanho de uma Box mãe sem afetar o tamanho da Box, use o modificador matchParentSize.

O matchParentSize só está disponível em um escopo de Box, o que significa que ele se aplica apenas a filhos diretos das funções Box que podem ser compostas.

No exemplo abaixo, o Spacer filho tem o tamanho da Box mãe, que, por sua vez, assume o tamanho dos filhos maiores, ArtistCard neste caso.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

Plano de fundo cinza como preenchimento do contêiner

Se fillMaxSize fosse usado em vez de matchParentSize, o Spacer ocuparia todo o espaço disponibilizado para o pai, fazendo com que o pai fosse expandido e preenchesse todo o espaço disponível.

Plano de fundo cinza como preenchimento da tela

weight em Row e Column

Conforme abordado na seção anterior sobre preenchimento e tamanho, por padrão, o tamanho de um elemento combinável é definido pelo conteúdo que ele agrupa. Você pode definir o tamanho de um elemento combinável como flexível no pai usando o modificador weight, disponível apenas em RowScope, e ColumnScope.

Vamos considerar uma Row contendo duas Box que podem ser compostas. A primeira caixa recebe o dobro de weight da segunda, portanto, duas vezes a largura. Como a Row tem 210.dp de largura, a primeira Box tem 140.dp de largura e a segunda tem 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

A largura da imagem é o dobro da largura do texto

Como extrair e reutilizar modificadores

Vários modificadores podem ser encadeados para decorar ou aumentar um elemento combinável. Essa cadeia é criada pela interface Modifier, que representa uma lista ordenada e imutável de Modifier.Elements únicos.

Cada Modifier.Element representa um comportamento individual, como comportamentos de layout, desenho e gráficos, todos os comportamentos relacionados a gestos, foco e semântica, bem como eventos de entrada do dispositivo. A ordem deles é importante: os elementos modificadores que são adicionados primeiro serão aplicados primeiro.

Às vezes, pode ser vantajoso reutilizar as mesmas instâncias de cadeia de modificadores em vários elementos combináveis, extraindo-os em variáveis e os elevando para escopos mais altos. Isso pode melhorar a legibilidade do código ou ajudar a melhorar a performance do app por alguns motivos:

  • A nova alocação dos modificadores não acontece quando a recomposição ocorre para elementos de composição.
  • As cadeias de modificadores podem ser muito longas e complexas. Portanto, reutilizar a mesma instância de uma cadeia pode aliviar a carga de trabalho de que o ambiente de execução do Compose precisa ao compará-las.
  • Essa extração promove a limpeza, a consistência e a manutenção do código em toda a base de código.

Práticas recomendadas para reutilizar modificadores

Crie suas próprias cadeias de Modifier e as extraia para reutilizá-las em vários componentes de composição. Não há problema em salvar modificadores, já que eles são objetos semelhantes a dados:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

Como extrair e reutilizar modificadores ao observar a mudança frequente de estado

Ao observar estados que mudam com frequência dentro de elementos de composição, como estados de animação ou scrollState, pode haver uma quantidade significativa de recomposições realizadas. Nesse caso, seus modificadores vão ser alocados em cada recomposição e, possivelmente, para cada frame:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

Em vez disso, você pode criar, extrair e reutilizar a mesma instância do modificador e transmiti-la para o elemento combinável da seguinte forma:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

Como extrair e reutilizar modificadores sem escopo

Os modificadores podem não ter escopo ou ter escopo para um elemento combinável específico. No caso de modificadores sem escopo, é possível extraí-los facilmente fora dos elementos combináveis como variáveis simples:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

Isso pode ser muito útil, principalmente quando combinado com layouts lentos. Na maioria dos casos, é recomendável que todos os itens possivelmente significativos tenham os mesmos modificadores:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

Como extrair e reutilizar modificadores com escopo

Ao lidar com modificadores com escopo em determinados elementos de composição, é possível extraí-los para o nível mais alto possível e reutilizá-los quando apropriado:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

Você só precisa transmitir os modificadores extraídos e com escopo para os filhos diretos com o mesmo escopo. Consulte a seção Segurança de escopo no Compose para mais informações sobre por que isso é importante:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

Como encadear ainda mais modificadores extraídos

Para encadear ou anexar ainda mais as cadeias de modificadores extraídas, chame a função .then():

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

Lembre-se de que a ordem dos modificadores é importante.

Saiba mais

Temos uma lista completa de modificadores com os parâmetros e escopos deles.

Para ver mais práticas sobre o uso de modificadores, consulte também o codelab de layouts básicos no Compose ou o repositório Agora no Android.

Para ver mais informações sobre modificadores personalizados e como criá-los, consulte a documentação sobre Layouts personalizados: como usar modificadores de layout.