💻 Exemplos: Capítulo 07: Modifiers Avançados, Gestos e Layout Customizado
Exemplos de Código: Capítulo 07 — Modifiers Avançados e Gestos no Compose
Especialização em Mobile Nativo com Android • Demonstrações Práticas Compiláveis
💻 Código de Demonstração: graphicsLayer, Gestos Multi-Touch e Layout Customizado
O código abaixo demonstra o controle de baixo nível de layouts e efeitos visuais no Jetpack Compose:
- Aplicação de
graphicsLayerpara transformações de rotação, escala e opacidade com aceleração por hardware (GPU). - Detecção de gestos multi-touch (arraste, zoom e rotação) com
pointerInputedetectTransformGestures. - Criação de um Layout Customizado medindo os filhos (
measurables.map { it.measure(constraints) }) e posicionando-os no canvas (layout() { placeable.placeRelative() }).
package br.com.portal.mobile.android.cap07
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.TouchApp
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlin.math.roundToInt
// =====================================================================
// 1. Cartão Interativo com Gestos Multi-Touch e graphicsLayer
// =====================================================================
@Composable
fun CartaoInterativoGestos(modifier: Modifier = Modifier) {
var escala by remember { mutableFloatStateOf(1f) }
var rotacao by remember { mutableFloatStateOf(0f) }
var deslocamento by remember { mutableStateOf(Offset.Zero) }
Box(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
contentAlignment = Alignment.Center
) {
ElevatedCard(
shape = RoundedCornerShape(24.dp),
modifier = Modifier
.offset { IntOffset(deslocamento.x.roundToInt(), deslocamento.y.roundToInt()) }
.graphicsLayer(
scaleX = maxOf(0.6f, minOf(escala, 3f)),
scaleY = maxOf(0.6f, minOf(escala, 3f)),
rotationZ = rotacao,
shadowElevation = 16f
)
.pointerInput(Unit) {
// Detecta Pinça (Pinch-to-zoom), Rotação e Pan simultaneamente
detectTransformGestures { _, pan, zoom, rotation ->
escala *= zoom
rotacao += rotation
deslocamento += pan
}
}
.size(240.dp, 160.dp)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
imageVector = Icons.Default.TouchApp,
contentDescription = null,
modifier = Modifier.size(48.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Arraste, Gire e Dê Zoom",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
}
}
}
// =====================================================================
// 2. Layout Customizado: DiagonalStackLayout
// =====================================================================
/**
* Mede e posiciona seus nós filhos em cascata diagonal decrescente.
*/
@Composable
fun DiagonalStackLayout(
modifier: Modifier = Modifier,
espacamentoX: Int = 30,
espacamentoY: Int = 40,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// 1. Fase de Medição: Mede cada filho com as restrições fornecidas
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
// Calcula a largura e altura total acumulada necessária
var larguraTotal = 0
var alturaTotal = 0
placeables.forEachIndexed { index, placeable ->
larguraTotal = maxOf(larguraTotal, placeable.width + (index * espacamentoX))
alturaTotal = maxOf(alturaTotal, placeable.height + (index * espacamentoY))
}
// 2. Fase de Posicionamento (Placement)
layout(larguraTotal, alturaTotal) {
var posX = 0
var posY = 0
placeables.forEach { placeable ->
placeable.placeRelative(x = posX, y = posY)
posX += espacamentoX
posY += espacamentoY
}
}
}
}
// =====================================================================
// 3. Previews
// =====================================================================
@Preview(showBackground = true)
@Composable
fun DiagonalLayoutPreview() {
MaterialTheme {
DiagonalStackLayout(modifier = Modifier.padding(24.dp)) {
Box(modifier = Modifier.size(100.dp, 60.dp).background(Color(0xFFE91E63), RoundedCornerShape(8.dp)))
Box(modifier = Modifier.size(100.dp, 60.dp).background(Color(0xFF9C27B0), RoundedCornerShape(8.dp)))
Box(modifier = Modifier.size(100.dp, 60.dp).background(Color(0xFF2196F3), RoundedCornerShape(8.dp)))
}
}
}