Capítulo 07: Modifiers Avançados, Layout Phases e Custom Layouts
Capítulo 07: Modifiers Avançados, Layout Phases e Custom Layouts
Especialização em Mobile Nativo com Android • Kotlin 2.0 & Jetpack Compose
🗺️ Mapa Conceitual das Fases de Execução do Compose e Cadeia de Modifiers
flowchart TD
subgraph PHASES["As 3 Fases do Pipeline do Compose"]
P1["1. Fase de Composição: Constrói a árvore de nós da UI"]
P2["2. Fase de Layout: Mede dimensões (Constraints) e posiciona nós (Coordinates)"]
P3["3. Fase de Desenho: Gravação de comandos de renderização na GPU (Canvas)"]
P1 --> P2 --> P3
end
subgraph MODIFIERS["A Regra da Ordem dos Modifiers (Top-Down Execution)"]
M1["Modifier.size(100.dp) -> Define limites de medição"]
M2["Modifier.background(Color.Blue) -> Pinta fundo nos limites atuais"]
M3["Modifier.padding(16.dp) -> Encolhe constraints para filhos internos"]
M4["Modifier.clickable { } -> Efeito ripple e área de toque restrita"]
M1 --> M2 --> M3 --> M4
end
subgraph CUSTOM["Layout Customizado (Layout Composable)"]
MEASURE["MeasureScope: Medição dos Measurables"]
PLACE["Placeable: Posicionamento manual no plano cartesiano (X, Y)"]
MEASURE --> PLACE
end
P2 -.-> MODIFIERS
P2 -.-> CUSTOM
style P1 fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style P2 fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style P3 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style M2 fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style PLACE fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. O Papel dos Modifiers e a Arquitetura de Fases
No Jetpack Compose, os componentes não possuem dezenas de propriedades internas de estilização como margens, bordas ou alinhamentos embutidas na sua assinatura. Em vez disso, todo o comportamento extrínseco e apresentação visual é delegado ao objeto Modifier:
- A Ordem Importa Radicalmente: Modifiers operam como uma cadeia encadeada de decoração aplicada de fora para dentro. Um
Modifier.padding(16.dp).background(Color.Red)desenhará o retângulo vermelho após aplicar o espaçamento (criando o efeito de margem externa). Inverter paraModifier.background(Color.Red).padding(16.dp)fará com que o fundo vermelho preencha todo o espaço e o padding empurre o conteúdo interno (efeito de preenchimento interno). - As Três Fases de Execução: Para otimizar a renderização, o Compose divide o trabalho em Composição (o que mostrar), Layout (onde e com qual tamanho colocar) e Desenho (como pintar). Se um valor muda apenas na fase de desenho (por exemplo, rotação ou transparência via
Modifier.graphicsLayer {}), o Compose pula completamente as fases de Composição e Layout, garantindo fluidez a 120 FPS. - Construção de Custom Layouts: Quando contêineres padrões como
Column,RowouBoxnão atendem a requisitos complexos (como gráficos circulares, layouts de cascata ou fluxos em tag-cloud), o Compose disponibiliza o composable de baixo nívelLayout().
📊 2. Comparativo de Impacto nas Fases de Execução
| Modificador / Técnica | Fase de Disparo | Impacto na Performance | Recomendação de Uso |
|---|---|---|---|
Modifier.offset(x, y) |
Fase de Layout | Recalcula medição e posicionamento a cada frame | Mudanças ocasionais de posição estática |
Modifier.offset { IntOffset(x, y) } |
Fase de Layout (sem recompor) | Pula Composição, executa apenas medição | Animações de translação baseadas em scroll ou gestos |
Modifier.graphicsLayer { alpha; rotationZ } |
Fase de Desenho Puro | Pula Composição e Layout; desenha direto via hardware | Animações contínuas de rotação, fade ou escala |
Modifier.background(Color) |
Fase de Desenho | Rápido, mas recompõe se a cor mudar | Cores de fundo estáticas |
Modifier.drawBehind { drawRect(...) } |
Fase de Desenho | Pinta no canvas sem alocar objetos no runtime | Gráficos geométricos customizados e sombras |
💻 3. Laboratório Prático: Custom Layout em Cascata (Flow/Cascade)
O código abaixo demonstra a criação de um layout customizado usando a função Layout do Compose para organizar itens sequencialmente com deslocamento diagonal escalonado:
package br.com.portal.mobile.android.cap07
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
// 1. Layout Customizado: Escadinha em Cascata
@Composable
fun EscadaDiagonalLayout(
espacamentoHorizontal: Dp = 16.dp,
deslocamentoVertical: Dp = 20.dp,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Mede cada filho respeitando as constraints do pai
val placeables: List<Placeable> = measurables.map { measurable ->
measurable.measure(constraints)
}
val stepX = espacamentoHorizontal.roundToPx()
val stepY = deslocamentoVertical.roundToPx()
// Calcula a largura e altura total acumulada pelo layout
val larguraTotal = placeables.mapIndexed { idx, p -> idx * stepX + p.width }.maxOrNull() ?: 0
val alturaTotal = placeables.mapIndexed { idx, p -> idx * stepY + p.height }.maxOrNull() ?: 0
// Define as dimensões do componente pai e posiciona cada filho
layout(larguraTotal, alturaTotal) {
var posX = 0
var posY = 0
placeables.forEach { placeable ->
placeable.placeRelative(x = posX, y = posY)
posX += stepX
posY += stepY
}
}
}
}
// 2. Uso Prático na Interface
@Composable
fun DemonstracaoLayoutCascata() {
EscadaDiagonalLayout(
espacamentoHorizontal = 24.dp,
deslocamentoVertical = 28.dp,
modifier = Modifier.padding(16.dp)
) {
BadgePasso(numero = "1", cor = Color(0xFF1976D2))
BadgePasso(numero = "2", cor = Color(0xFF388E3C))
BadgePasso(numero = "3", cor = Color(0xFFF57C00))
BadgePasso(numero = "4", cor = Color(0xFF7B1FA2))
}
}
@Composable
fun BadgePasso(numero: String, cor: Color) {
Box(
modifier = Modifier
.size(54.dp)
.background(cor, shape = RoundedCornerShape(12.dp)),
contentAlignment = androidx.compose.ui.Alignment.Center
) {
Text(text = numero, color = Color.White, fontSize = 20.sp)
}
}
⚠️ 4. Armadilhas Comuns no Encadeamento de Modifiers
- Colocar
clickableApóspadding: Se você aplicarclickabledepois de umpadding, a área de clique será limitada apenas à área reduzida interna do componente, gerando sensação de falta de responsividade ao toque do usuário. Sempre declare oclickableantes do padding para ampliar a zona de toque acessível. - Re-criar Modifiers Desnecessariamente em Funções Utilitárias: Ao criar funções que retornam
Modifier, evite encadear instâncias novas a cada recomposição. Prefira estenderModifiercriando extensões simples ou usandocomposed {}apenas quando o modificador exigir estados comremember. - Ignorar o Parâmetro
modifiernos Componentes: Todo Composable que renderiza elementos visuais deve aceitar um parâmetromodifier: Modifier = Modifiercomo primeiro argumento opcional e aplicá-lo ao nó raiz. Omitir esse parâmetro impede que o componente receba constraints ou margens personalizadas de quem o consome.