O que é Modifier em Kotlin?

Modifier é o mecanismo do Jetpack Compose para decorar, configurar e modificar componentes de UI. Ele controla aparencia (tamanho, padding, cor de fundo), comportamento (clique, scroll, foco) e layout (alinhamento, preenchimento, posicionamento) de qualquer Composable.

Em vez de definir atributos em XML como no sistema antigo de Views, você encadeia chamadas de Modifier em uma sintaxe fluente e expressiva. A ordem das chamadas importa e afeta o resultado visual.

Sintaxe básica

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun CartaoSimples() {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(
            text = "Conteudo do cartao",
            modifier = Modifier.padding(24.dp)
        )
    }
}

O Modifier começa como um objeto companheiro (singleton) e cada chamada retorna um novo Modifier com a modificacao aplicada. A cadeia e imutavel: cada passo cria um novo objeto.

A ordem importa

A ordem em que você encadeia modifiers afeta diretamente o resultado:

// Padding ANTES do background: espaco transparente ao redor do fundo
@Composable
fun ExemploA() {
    Box(
        modifier = Modifier
            .padding(16.dp)
            .background(Color.Blue)
            .size(100.dp)
    )
}

// Background ANTES do padding: fundo preenche o espaco do padding
@Composable
fun ExemploB() {
    Box(
        modifier = Modifier
            .background(Color.Blue)
            .padding(16.dp)
            .size(100.dp)
    )
}

Pense nos modifiers como camadas aplicadas de fora para dentro. O primeiro modifier na cadeia e a camada mais externa.

Modifiers de tamanho

@Composable
fun ExemplosTamanho() {
    // Tamanho fixo
    Box(modifier = Modifier.size(100.dp))

    // Largura e altura separadas
    Box(modifier = Modifier.width(200.dp).height(50.dp))

    // Preencher todo o espaco disponivel
    Box(modifier = Modifier.fillMaxSize())

    // Preencher apenas largura
    Box(modifier = Modifier.fillMaxWidth())

    // Preencher uma fracao
    Box(modifier = Modifier.fillMaxWidth(0.5f)) // 50% da largura

    // Tamanho minimo e maximo
    Box(modifier = Modifier.widthIn(min = 100.dp, max = 300.dp))

    // Tamanho baseado em aspecto
    Box(modifier = Modifier.aspectRatio(16f / 9f))
}

Modifiers de aparencia

@Composable
fun ExemplosAparencia() {
    Box(
        modifier = Modifier
            .size(200.dp)
            .background(
                color = Color.Blue,
                shape = RoundedCornerShape(16.dp)
            )
            .border(
                width = 2.dp,
                color = Color.DarkGray,
                shape = RoundedCornerShape(16.dp)
            )
            .shadow(
                elevation = 8.dp,
                shape = RoundedCornerShape(16.dp)
            )
            .alpha(0.9f)
    )
}

Modifiers de interação

@Composable
fun ExemplosInteracao() {
    var clicado by remember { mutableStateOf(false) }

    Box(
        modifier = Modifier
            .clickable { clicado = !clicado }
            .combinedClickable(
                onClick = { /* clique simples */ },
                onLongClick = { /* clique longo */ },
                onDoubleClick = { /* clique duplo */ }
            )
    )

    // Scroll
    Column(
        modifier = Modifier.verticalScroll(rememberScrollState())
    ) {
        // Conteudo scrollavel
    }

    // Arrastar
    Box(
        modifier = Modifier.draggable(
            orientation = Orientation.Horizontal,
            state = rememberDraggableState { delta ->
                // Processar delta do arrasto
            }
        )
    )
}

Criando Modifiers customizados

Você pode criar extension functions em Modifier para reutilizar combinacoes:

fun Modifier.cartaoPadrao(): Modifier = this
    .fillMaxWidth()
    .padding(horizontal = 16.dp, vertical = 8.dp)
    .shadow(4.dp, RoundedCornerShape(12.dp))
    .background(Color.White, RoundedCornerShape(12.dp))
    .padding(16.dp)

@Composable
fun MeuCartao(titulo: String) {
    Column(modifier = Modifier.cartaoPadrao()) {
        Text(text = titulo)
    }
}

Para modifiers que dependem de estado do Compose, use composed:

fun Modifier.shimmerEffect(): Modifier = composed {
    val transition = rememberInfiniteTransition()
    val alpha by transition.animateFloat(
        initialValue = 0.2f,
        targetValue = 1f,
        animationSpec = infiniteRepeatable(
            animation = tween(1000),
            repeatMode = RepeatMode.Reverse
        )
    )
    this.alpha(alpha)
}

Modifier como parametro

O padrão recomendado e aceitar Modifier como parametro em Composables reutilizaveis:

@Composable
fun BotaoCustom(
    texto: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier // Valor padrao: Modifier vazio
) {
    Button(
        onClick = onClick,
        modifier = modifier
            .height(48.dp)
            .fillMaxWidth()
    ) {
        Text(text = texto)
    }
}

// Uso: o chamador pode adicionar modifiers extras
@Composable
fun Tela() {
    BotaoCustom(
        texto = "Salvar",
        onClick = { /* ... */ },
        modifier = Modifier.padding(16.dp) // Adicionado pelo chamador
    )
}

O parametro modifier deve ser o primeiro parametro opcional e ter Modifier (vazio) como valor padrão. Isso segue as convenções da API do Compose.

Modifier.then para composição condicional

@Composable
fun ComponenteCondicional(habilitado: Boolean) {
    val modifierBase = Modifier.fillMaxWidth().padding(16.dp)

    val modifierFinal = if (habilitado) {
        modifierBase.then(Modifier.clickable { /* acao */ })
    } else {
        modifierBase.then(Modifier.alpha(0.5f))
    }

    Box(modifier = modifierFinal) {
        Text("Conteudo")
    }
}

Quando usar Modifier

  • Sempre: praticamente todo Composable aceita e deveria aceitar um parametro Modifier. E a forma padrão de configurar aparencia e comportamento.
  • Componentes reutilizaveis: sempre aceite modifier como parametro para permitir customizacao pelo chamador.
  • Estilizacao consistente: crie extension functions de Modifier para padrões visuais reutilizaveis.
  • Layout: Modifiers controlam como componentes se posicionam e ocupam espaco.

Casos de Uso no Mundo Real

  1. Design systems corporativos: Empresas criam bibliotecas de Modifiers customizados que encapsulam padrões visuais do design system (espacamentos, bordas, sombras, cores). Isso garante consistencia visual em todas as telas da aplicação sem duplicar código de estilizacao.

  2. Acessibilidade em aplicações Android: Modifiers como semantics, contentDescription e testTag sao usados para tornar aplicações acessiveis a leitores de tela e para facilitar testes automatizados de UI. Equipes adicionam esses modifiers sistematicamente em componentes interativos.

  3. Animacoes e transicoes de UI: Modifiers como animateContentSize, graphicsLayer e combinacoes com Modifier.offset sao usados para criar animacoes fluidas de entrada, saida e transicao entre estados de UI, como expandir/colapsar cards ou deslizar itens para deletar.

  4. Layouts responsivos para diferentes tamanhos de tela: Usando Modifier.fillMaxWidth(), widthIn() e aspectRatio() combinados com lógica condicional baseada no tamanho da janela, desenvolvedores criam layouts que se adaptam a celulares, tablets e desktops com Compose Multiplatform.

Boas Praticas

  • Sempre aceite Modifier como primeiro parametro opcional em Composables reutilizaveis, com valor padrão Modifier (vazio), seguindo as convencoes oficiais do Compose.
  • Aplique o modifier recebido como parametro no elemento raiz do Composable antes de encadear seus próprios modifiers, para que o chamador tenha controle sobre o posicionamento e espaco externo.
  • Crie extension functions de Modifier para combinacoes de estilos que se repetem no projeto, como Modifier.cartaoPadrao() ou Modifier.botaoDestaque(), promovendo reutilização e consistencia.
  • Preste atencao na ordem de encadeamento: modifiers sao aplicados de fora para dentro. Coloque padding antes de background para criar espaco transparente ao redor, ou depois para criar padding interno colorido.
  • Evite tamanhos fixos com Modifier.size() quando o componente precisa se adaptar a diferentes telas. Prefira combinacoes de fillMaxWidth(), widthIn() e heightIn() para layouts flexiveis.

Perguntas Frequentes

P: Por que a ordem dos Modifiers afeta o resultado visual? R: Modifiers sao aplicados sequencialmente como camadas de fora para dentro. Cada modifier envolve o anterior, alterando o espaco disponivel e a aparencia. Por exemplo, padding antes de background cria espaco transparente ao redor do fundo, enquanto background antes de padding faz com que a cor de fundo preencha também a area de padding.

P: Qual a diferenca entre Modifier.then() e encadear modifiers diretamente? R: Encadear diretamente (.padding().background()) e o uso comum. Modifier.then() e útil para composicao condicional ou para combinar dois objetos Modifier separados em um só. Ambos produzem o mesmo resultado quando aplicados na mesma ordem.

P: Como criar um Modifier que depende de estado do Compose? R: Use a função composed {} para criar modifiers que precisam de estado do Compose, como remember ou animateFloat. Dentro do bloco composed, você tem acesso ao contexto de composicao e pode usar hooks normalmente.

P: O Modifier afeta a performance de recomposicao? R: Modifiers sao objetos imutaveis e o Compose os compara eficientemente durante a recomposicao. Porem, criar modifiers dentro de lambdas sem remember pode causar realocacoes desnecessarias. Para modifiers estaticos, defina-os fora da função Composable ou use remember para memoiza-los.

Erros comuns

  1. Ignorar a ordem dos modifiers: padding antes de background e diferente de background antes de padding. Sempre teste visualmente com @Preview.

  2. Nao aceitar Modifier como parametro: componentes que não aceitam Modifier não podem ser estilizados externamente, reduzindo a reutilização.

  3. Usar Modifier.size fixo em excesso: tamanhos fixos não se adaptam a diferentes telas. Prefira fillMaxWidth com padding ou widthIn com limites.

  4. Criar modifiers dentro de lambdas de recomposicao: se um modifier e criado com composed ou remember, certifique-se de que não esta sendo recriado desnecessariamente.

  5. Nao encadear com o modifier recebido: quando você recebe modifier como parametro, aplique-o no elemento raiz antes de adicionar seus próprios modifiers.

Termos relacionados

  • Composable: função anotada com @Composable que descreve UI e aceita Modifiers.
  • State: estado reativo que pode ser usado em conjunto com modifiers condicionais.
  • Layout: sistema de posicionamento do Compose onde Modifiers influenciam tamanho e posicao.
  • padding/size/fillMaxWidth: funções de extensao de Modifier para configurar dimensoes.
  • clickable: modifier de interação que torna componentes clicaveis.
  • Recomposicao: processo que reaplica modifiers quando o estado muda.

Modifier e o canivete suico do Jetpack Compose. Dominar a encadeamento de modifiers, entender a importancia da ordem e criar modifiers customizados reutilizaveis são habilidades fundamentais para construir interfaces bonitas e funcionais em Kotlin.