Neste tutorial, você vai aprender a construir layouts com Jetpack Compose, a toolkit declarativa do Android para construcao de interfaces. Vamos explorar os composables fundamentais — Row, Column, Box — além de listas com LazyColumn, estruturas de tela com Scaffold é o poderoso sistema de Modifier. Se você já deu os primeiros passos com Compose no tutorial de Jetpack Compose básico, este guia vai expandir bastante o seu repertorio.

A Filosofia de Layouts no Compose

Diferente do sistema de Views clássico do Android, onde layouts são definidos em XML, no Compose tudo e feito em Kotlin. Cada elemento de interface é uma função Composable que descreve o que deve aparecer na tela. Nao existe mais LinearLayout, RelativeLayout ou ConstraintLayout em XML — agora você compoe a interface empilhando funções.

Essa mudanca traz uma vantagem enorme: o layout é o comportamento vivem no mesmo lugar, facilitando a leitura é a manutenção do código.

Column: Organizando Elementos na Vertical

O Column é o equivalente ao antigo LinearLayout com orientacao vertical. Ele empilha seus filhos de cima para baixo.

@Composable
fun CartaoUsuario() {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Text(
            text = "Maria Silva",
            style = MaterialTheme.typography.headlineSmall
        )
        Text(
            text = "Desenvolvedora Android",
            style = MaterialTheme.typography.bodyMedium
        )
        Text(
            text = "São Paulo, Brasil",
            style = MaterialTheme.typography.bodySmall
        )
    }
}

O parametro verticalArrangement controla como o espaco e distribuído entre os filhos. As opções mais comuns são Arrangement.Top, Arrangement.Center, Arrangement.SpaceBetween, Arrangement.SpaceEvenly e Arrangement.spacedBy().

Para alinhar os filhos horizontalmente dentro de um Column, use o parametro horizontalAlignment:

Column(
    modifier = Modifier.fillMaxWidth(),
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Text("Texto centralizado")
    Button(onClick = { }) {
        Text("Botao centralizado")
    }
}

Row: Organizando Elementos na Horizontal

O Row funciona da mesma forma que o Column, mas na horizontal — os filhos são posicionados da esquerda para a direita.

@Composable
fun BarraDeAcoes() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        IconButton(onClick = { }) {
            Icon(Icons.Default.ArrowBack, contentDescription = "Voltar")
        }
        Text(
            text = "Detalhes",
            style = MaterialTheme.typography.titleMedium
        )
        IconButton(onClick = { }) {
            Icon(Icons.Default.MoreVert, contentDescription = "Mais opções")
        }
    }
}

Um recurso muito útil dentro de Row e o Modifier.weight(), que distribui o espaco disponivel proporcionalmente, semelhante ao layout_weight do antigo LinearLayout:

Row(modifier = Modifier.fillMaxWidth()) {
    Text(
        text = "Nome do produto",
        modifier = Modifier.weight(1f)
    )
    Text(
        text = "R$ 49,90",
        modifier = Modifier.width(100.dp),
        textAlign = TextAlign.End
    )
}

Aqui, o nome do produto ocupa todo o espaco restante enquanto o preco mantém uma largura fixa.

Box: Sobreposicao de Elementos

O Box permite empilhar elementos um sobre o outro, como camadas. E o equivalente ao FrameLayout do sistema clássico.

@Composable
fun ImagemComBadge() {
    Box(
        modifier = Modifier.size(120.dp)
    ) {
        Image(
            painter = painterResource(id = R.drawable.foto_perfil),
            contentDescription = "Foto de perfil",
            modifier = Modifier.fillMaxSize(),
            contentScale = ContentScale.Crop
        )
        Badge(
            modifier = Modifier.align(Alignment.BottomEnd)
        ) {
            Text("3")
        }
    }
}

O parametro contentAlignment do Box define o alinhamento padrão dos filhos, enquanto Modifier.align() permite posicionar cada filho individualmente.

O Sistema de Modifier

O Modifier e provavelmente o conceito mais importante de todo o Jetpack Compose. Ele e uma cadeia de decoracoes que você aplica a qualquer composable para controlar tamanho, padding, alinhamento, cliques, cor de fundo e muito mais.

Text(
    text = "Elemento estilizado",
    modifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)
        .background(
            color = MaterialTheme.colorScheme.primaryContainer,
            shape = RoundedCornerShape(8.dp)
        )
        .padding(12.dp)
        .clickable { /* acao ao clicar */ }
)

A ordem dos modifiers importa. No exemplo acima, o primeiro padding cria espaco externo (como margin), o background aplica a cor, e o segundo padding cria espaco interno entre o fundo e o texto. Inverter a ordem muda completamente o resultado visual.

Alguns dos modifiers mais usados no dia a dia:

  • fillMaxWidth() / fillMaxHeight() / fillMaxSize() — preencher o espaco disponivel
  • width() / height() / size() — dimensoes fixas
  • padding() — espacamento interno ou externo (depende da ordem)
  • background() — cor ou forma de fundo
  • clip() — recortar o conteudo com uma forma
  • border() — adicionar borda
  • clickable {} — tornar o elemento clicavel
  • weight() — distribuir espaco dentro de Row ou Column

LazyColumn e LazyRow: Listas Eficientes

Para listas longas, usar Column com muitos itens seria um desastre de performance, porque todos os itens seriam compostos de uma vez. E ai que entram LazyColumn e LazyRow, equivalentes ao RecyclerView do sistema clássico. Eles só compoem os itens que estao visiveis na tela.

@Composable
fun ListaDeProdutos(produtos: List<Produto>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = produtos,
            key = { it.id }
        ) { produto ->
            CartaoProduto(produto)
        }
    }
}

@Composable
fun CartaoProduto(produto: Produto) {
    Card(
        modifier = Modifier.fillMaxWidth()
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = produto.nome,
                style = MaterialTheme.typography.titleMedium
            )
            Text(
                text = "R$ ${"%.2f".format(produto.preco)}",
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}

O parametro key e muito importante: ele permite que o Compose identifique cada item unicamente, otimizando recomposicoes quando a lista muda.

Para listas horizontais, basta trocar para LazyRow:

LazyRow(
    contentPadding = PaddingValues(horizontal = 16.dp),
    horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
    items(categorias) { categoria ->
        ChipCategoria(categoria)
    }
}

Se você já usou RecyclerView, vai notar que o código com LazyColumn e significativamente mais simples — não há necessidade de Adapter, ViewHolder ou layout XML separado.

Scaffold: Estrutura de Tela Completa

O Scaffold e um composable que fornece a estrutura básica de uma tela Material Design, incluindo slots para TopAppBar, BottomBar, FloatingActionButton e conteudo principal.

@Composable
fun TelaPrincipal() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Meu Aplicativo") },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.primary,
                    titleContentColor = MaterialTheme.colorScheme.onPrimary
                )
            )
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { }) {
                Icon(Icons.Default.Add, contentDescription = "Adicionar")
            }
        },
        bottomBar = {
            NavigationBar {
                NavigationBarItem(
                    selected = true,
                    onClick = { },
                    icon = { Icon(Icons.Default.Home, contentDescription = "Inicio") },
                    label = { Text("Inicio") }
                )
                NavigationBarItem(
                    selected = false,
                    onClick = { },
                    icon = { Icon(Icons.Default.Settings, contentDescription = "Config") },
                    label = { Text("Config") }
                )
            }
        }
    ) { paddingValues ->
        LazyColumn(
            modifier = Modifier.padding(paddingValues)
        ) {
            items(50) { indice ->
                Text(
                    text = "Item $indice",
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                )
            }
        }
    }
}

Repare no parametro paddingValues que o Scaffold passa para o conteudo. Ele inclui o espaco ocupado pela TopAppBar e pela BottomBar, garantindo que o conteudo não fique escondido atras deles. Sempre aplique esse padding ao conteudo principal.

ConstraintLayout no Compose

Para layouts mais complexos onde os elementos precisam se posicionar em relação uns aos outros, o Compose oferece o ConstraintLayout. Ele exige uma dependência adicional:

// build.gradle.kts
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0")
@Composable
fun PerfilUsuario() {
    ConstraintLayout(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        val (foto, nome, cargo) = createRefs()

        Image(
            painter = painterResource(id = R.drawable.avatar),
            contentDescription = "Avatar",
            modifier = Modifier
                .size(64.dp)
                .clip(CircleShape)
                .constrainAs(foto) {
                    start.linkTo(parent.start)
                    top.linkTo(parent.top)
                }
        )

        Text(
            text = "Ana Costa",
            style = MaterialTheme.typography.titleMedium,
            modifier = Modifier.constrainAs(nome) {
                start.linkTo(foto.end, margin = 12.dp)
                top.linkTo(foto.top)
            }
        )

        Text(
            text = "Engenheira de Software",
            style = MaterialTheme.typography.bodyMedium,
            modifier = Modifier.constrainAs(cargo) {
                start.linkTo(nome.start)
                top.linkTo(nome.bottom, margin = 4.dp)
            }
        )
    }
}

Embora o ConstraintLayout esteja disponivel, na prática a maioria dos layouts no Compose pode ser resolvida combinando Row, Column e Box. Reserve o ConstraintLayout para casos onde as relacoes entre elementos são realmente complexas.

Layouts Responsivos

Para criar layouts que se adaptam a diferentes tamanhos de tela — celulares, tablets, desktops — você pode usar BoxWithConstraints, que fornece as dimensoes disponiveis:

@Composable
fun LayoutAdaptavel() {
    BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
        if (maxWidth > 600.dp) {
            // Layout para telas grandes: duas colunas
            Row(modifier = Modifier.fillMaxSize()) {
                ListaDeProdutos(
                    produtos = produtos,
                    modifier = Modifier.weight(1f)
                )
                DetalhesProduto(
                    produto = produtoSelecionado,
                    modifier = Modifier.weight(1f)
                )
            }
        } else {
            // Layout para telas pequenas: coluna unica
            ListaDeProdutos(
                produtos = produtos,
                modifier = Modifier.fillMaxSize()
            )
        }
    }
}

Essa abordagem e fundamental para apps que rodam em dispositivos com tamanhos de tela variados, especialmente com o crescimento do Kotlin Multiplatform e do Compose Multiplatform.

Conclusão

Dominar layouts no Jetpack Compose e questao de entender bem tres coisas: os composables de posicionamento (Column, Row, Box), o sistema de Modifier e os componentes de listas lazy. Com essas ferramentas, você consegue construir praticamente qualquer interface. O Scaffold organiza a estrutura geral da tela, e para casos excepcionais o ConstraintLayout esta a disposicao.

A melhor forma de fixar esses conceitos e colocando a mao na massa. Pegue um design qualquer — pode ser uma tela do seu app favorito — e tente reproduzi-lo usando apenas Compose. Você vai perceber que a combinacao de funções, lambdas e composables torna o processo muito mais fluido do que o antigo sistema de Views em XML.