📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.

📊 P18: Radar de Habilidades (Canvas 2D & Gráficos no Compose)

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).

Fluxo Completo de Telas do Radar de Habilidades


📱 Galeria de Telas da Aplicação

1. Guerreiro (Tanque) 2. Ladino (Furtivo) 3. Mago (Arcano)
Tela 1 - Guerreiro Tela 2 - Ladino Tela 3 - Mago
Foco em Força e Defesa em vermelho carmesim. Foco em Agilidade e Furtividade em verde. Foco em Magia Suprema em roxo arcano.

✅ Pré-requisitos e Continuidade

Antes de iniciar este laboratório, certifique-se de ter compreendido:


🎯 Objetivos de Aprendizagem

Ao concluir este projeto autoguiado, você será capaz de:

  1. Dominar o Canvas do Jetpack Compose: Usar primitivas gráficas (drawLine, drawCircle, drawPath) com o DrawScope.
  2. Aplicar Trigonometria no Espaço de Coordenadas 2D: Mapear 5 eixos radiais a partir do centro da tela usando (radius * cos(angle)) e (radius * sin(angle)).
  3. Construir Polígonos Fechados com a classe Path: Unir vértices geométricos com path.moveTo(), path.lineTo() e path.close().
  4. Animar Vértices com animateFloatAsState: Transicionar valores de atributos suavemente entre diferentes personagens sem travamentos na Main Thread.
  5. Implementar Efeitos Contínuos com rememberInfiniteTransition: Criar o feixe giratório de 360° do sonar holográfico.
  6. Aplicar Boas Práticas de Otimização Gráfica: Evitar alocações desnecessárias no laço de desenho (Draw Loop) para economizar bateria e GPU.

🏗️ Matemática do Radar Pentagonal

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]

🛡️ Boas Práticas de Performance e Otimização Gráfica

[!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:

  1. Zero Alocações no Draw Loop: Cálculos de estruturas fixas são reutilizados;
  2. Aceleração por Hardware Nativa: O DrawScope aproveita a pipeline Vulkan / OpenGL do Android;
  3. Interpolações Não-Bloqueantes: O animateFloatAsState delega a interpolação ao subsistema de animações do Compose;
  4. Higiene de Versionamento: Arquivo .gitignore configurado para isolar caches locais.

📖 Dicionário Técnico do Projeto

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).

🛠️ Passo a Passo de Implementação

🚀 Passo 1: Criando o Novo Projeto no Android Studio

  1. Abra o Android Studio e clique em New Project (ou File > New > New Project).
  2. Selecione o template Empty Activity (com o ícone do Jetpack Compose 🌌).
  3. Preencha as configurações do projeto:
    • Name: Radar de Habilidades (ou android_p18_radar_canvas)
    • Package name: br.com.curso.radar
    • Save location: Pasta do seu projeto no repositório
    • Language: Kotlin
    • Minimum SDK: API 24 ("Nougat"; Android 7.0) ou superior
    • Build configuration language: Groovy DSL (build.gradle) ou Kotlin DSL (build.gradle.kts)
  4. Clique em Finish e aguarde o Gradle sincronizar.

📦 Passo 2: Configurando Dependências no Gradle (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'
}

📄 Passo 3: Manifesto do Aplicativo (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>

🛡️ Passo 4: Modelagem de Classes RPG (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)
)

🎨 Passo 5: Computação Gráfica e Desenho no Canvas

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))
}

🔍 Guia de Diagnóstico & Resolução de Problemas (Troubleshooting)

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.

🏆 Desafios e Upgrades (Mão na Massa!)

  1. ⚖️ Comparador de Duas Classes (Overlay): Desenhe dois polígonos simultâneos no mesmo radar para comparar o Guerreiro vs Mago com cores contrastantes.
  2. 🏷️ Rótulos Flutuantes nos Vértices: Renderize os nomes dos atributos dinamicamente nas pontas usando drawContext.canvas.nativeCanvas.drawText().
  3. 🎚️ Slider Customizador de Atributos: Adicione controles deslizantes (Slider) para o jogador distribuir 100 pontos de atributos livremente.

📖 Gabarito Oficial de Código (Para Conferência)

Disponível em: app/src/main/java/br/com/curso/radar/MainActivity.kt.


🚀 Como Executar no Laboratório

1. Abra o terminal na pasta deste projeto

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

2. Execute a aplicação e os testes

./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.