📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.
Bem-vindo à sétima etapa da Fase 7: Arquitetura Avançada & Computação Gráfica! 📊
Neste projeto prático, você mergulhará nos fundamentos da computação gráfica 2D no Android utilizando a poderosa API Canvas e o DrawScope do Jetpack Compose. Você aprenderá a calcular coordenadas polares trigonométricas (sin, cos) para renderizar um gráfico de teia pentagonal (Spider / Radar Chart) completo com interpolação fluida de vértices e feixe de varredura holográfica!
Com a temática de fichas de RPG (Guerreiro, Ladino, Mago, Paladino, Caçadora), o aplicativo analisa atributos como Força, Defesa, Agilidade, Furtividade e Magia, renderizando polígonos coloridos que se transformam dinamicamente e com alta eficiência de hardware (sem sobrecarregar a GPU ou esgotar a bateria do usuário).
| 1. Guerreiro (Tanque) | 2. Ladino (Furtivo) | 3. Mago (Arcano) |
|---|---|---|
| Foco em Força e Defesa em vermelho carmesim. | Foco em Agilidade e Furtividade em verde. | Foco em Magia Suprema em roxo arcano. |
Antes de iniciar este laboratório, certifique-se de ter compreendido:
Ao concluir este projeto autoguiado, você será capaz de:
Canvas do Jetpack Compose: Usar primitivas gráficas (drawLine, drawCircle, drawPath) com o DrawScope.(radius * cos(angle)) e (radius * sin(angle)).Path: Unir vértices geométricos com path.moveTo(), path.lineTo() e path.close().animateFloatAsState: Transicionar valores de atributos suavemente entre diferentes personagens sem travamentos na Main Thread.rememberInfiniteTransition: Criar o feixe giratório de 360° do sonar holográfico.graph TD
A[Centro da Tela Offset width/2, height/2] --> B[Define Raio Máximo maxRadius = size.width/2 * 0.72]
B --> C[Divide a circunferência em 5 ângulos: 2 * PI * j / 5 - PI/2]
C --> D[Gera Teias Concêntricas: 25%, 50%, 75%, 100%]
D --> E[Traça Eixos Radiais do Centro até cada Vértice]
E --> F[Mapeia Atributos: statRadius = maxRadius * percentual]
F --> G[Path.lineTo conecta os 5 vértices calculados]
G --> H[drawPath preenche o miolo e traça o contorno iluminado]
[!IMPORTANT] Prevenção de Quedas de Frame (Jank) e Esgotamento de Bateria (Battery Drain): O bloco
Canvas { ... }é executado a cada frame durante animações (geralmente a 60 FPS ou 120 FPS). Alocar objetos pesados dentro do escopo de desenho faz o Garbage Collector pausar a renderização da aplicação.Diretrizes Adotadas:
- Zero Alocações no Draw Loop: Cálculos de estruturas fixas são reutilizados;
- Aceleração por Hardware Nativa: O
DrawScopeaproveita a pipeline Vulkan / OpenGL do Android;- Interpolações Não-Bloqueantes: O
animateFloatAsStatedelega a interpolação ao subsistema de animações do Compose;- Higiene de Versionamento: Arquivo
.gitignoreconfigurado para isolar caches locais.
| Termo / Componente | O que é e para que serve? |
|---|---|
| Canvas | Composable do Jetpack Compose que disponibiliza uma superfície de desenho 2D acelerada por hardware. |
| DrawScope | Contexto de desenho que expõe métodos como drawPath, drawLine, drawCircle e a dimensão size. |
| Coordenadas Polares | Sistema de coordenadas onde a posição de um ponto é determinada por uma distância (Raio) e um ângulo em radianos. |
| Path | Estrutura de geometria vetorial composta por linhas, curvas e arcos conectados. |
| Stroke | Estilo de renderização que desenha apenas o contorno da forma geométrica com espessura configurável. |
| rememberInfiniteTransition | Utilitário para gerenciar animações perpétuas em loop (como o feixe de rotação do radar). |
File > New > New Project).Radar de Habilidades (ou android_p18_radar_canvas)br.com.curso.radarKotlinAPI 24 ("Nougat"; Android 7.0) ou superiorGroovy DSL (build.gradle) ou Kotlin DSL (build.gradle.kts)build.gradle)Abra app > build.gradle:
dependencies {
implementation platform('androidx.compose:compose-bom:2024.02.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.activity:activity-compose:1.8.2'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
}
AndroidManifest.xml)Abra app > src > main > AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:label="Radar de Habilidades"
android:supportsRtl="true"
android:theme="@style/Theme.CartaoTreinador">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
HeroClass)Abra app > src > main > java > br > com > curso > radar > MainActivity.kt:
data class HeroClass(
val name: String,
val role: String,
val emoji: String,
val color: Color,
val strength: Float,
val agility: Float,
val stealth: Float,
val magic: Float,
val defense: Float
)
val rpgHeroes = listOf(
HeroClass("Guerreiro", "Tanque / Linha de Frente", "⚔️", Color(0xFFFF5252), 0.95f, 0.40f, 0.20f, 0.15f, 0.88f),
HeroClass("Ladino", "Assassino Furtivo", "🗡️", Color(0xFF00E676), 0.35f, 0.95f, 0.92f, 0.25f, 0.30f),
HeroClass("Mago", "Conjurador Arcano", "🔮", Color(0xFFB388FF), 0.15f, 0.45f, 0.35f, 0.98f, 0.25f),
HeroClass("Paladino", "Guardião Sagrado", "🛡️", Color(0xFFFFD54F), 0.80f, 0.30f, 0.10f, 0.70f, 0.95f),
HeroClass("Caçadora", "Ranger Élfica", "🏹", Color(0xFF00E5FF), 0.60f, 0.88f, 0.80f, 0.50f, 0.45f)
)
Canvas(modifier = Modifier.fillMaxSize()) {
val center = Offset(size.width / 2, size.height / 2)
val maxRadius = size.width / 2 * 0.72f
val numAxes = 5
val angleOffset = -Math.PI / 2
// 1. Teias Concêntricas
val steps = 4
for (step in 1..steps) {
val radius = maxRadius * (step.toFloat() / steps)
val teiaPath = Path()
for (j in 0 until numAxes) {
val angle = (2 * Math.PI * j / numAxes) + angleOffset
val x = center.x + (radius * cos(angle)).toFloat()
val y = center.y + (radius * sin(angle)).toFloat()
if (j == 0) teiaPath.moveTo(x, y) else teiaPath.lineTo(x, y)
}
teiaPath.close()
drawPath(teiaPath, Color.White.copy(alpha = 0.12f), style = Stroke(width = 1.5f))
}
// 2. Polígono Dinâmico dos Atributos
val stats = floatArrayOf(animStrength, animAgility, animStealth, animMagic, animDefense)
val heroPolygon = Path()
for (j in 0 until numAxes) {
val angle = (2 * Math.PI * j / numAxes) + angleOffset
val statRadius = maxRadius * stats[j]
val x = center.x + (statRadius * cos(angle)).toFloat()
val y = center.y + (statRadius * sin(angle)).toFloat()
if (j == 0) heroPolygon.moveTo(x, y) else heroPolygon.lineTo(x, y)
}
heroPolygon.close()
// Preenchimento e Contorno
drawPath(heroPolygon, heroColor.copy(alpha = 0.30f))
drawPath(heroPolygon, heroColor, style = Stroke(width = 3.5f))
}
| Sintoma Observado | Causa Provável | Como Resolver |
|---|---|---|
| O polígono desenha linhas cortadas ou fora de ordem | O ângulo inicial não começou no mesmo vértice ou o path.close() foi omitido. |
Mantenha o angleOffset = -Math.PI / 2 e certifique-se de chamar heroPolygon.close(). |
| A animação de troca de classe é brusca | Os valores dos atributos foram lidos diretamente de currentHero sem envolver em animateFloatAsState. |
Utilize val animStrength by animateFloatAsState(currentHero.strength). |
| O Canvas fica muito pequeno ou distorcido | O contêiner pai não definiu um tamanho explícito (size ou fillMaxSize). |
Envolva o Canvas em um Box(modifier = Modifier.size(280.dp)). |
| Deslocamento do radar para o canto superior esquerdo | O centro do desenho foi fixado em Offset(0,0) em vez de Offset(size.width / 2, size.height / 2). |
Utilize o ponto médio size.width / 2 e size.height / 2 como referência polar. |
drawContext.canvas.nativeCanvas.drawText().Slider) para o jogador distribuir 100 pontos de atributos livremente.Disponível em: app/src/main/java/br/com/curso/radar/MainActivity.kt.
No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd proj_aplicacoes_full_stack/projetos/android_p18_radar_canvas
./gradlew build
./gradlew test
# ou abrir no Android Studio e clicar em Run (Shift+F10)
[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code ou Android Studio, abra o projeto diretamente pela pasta
proj_aplicacoes_full_stack/projetos/android_p18_radar_canvas.