🎨 P04: Cartão de Treinador Pokémon (Jetpack Compose)

Bem-vindo ao futuro do desenvolvimento móvel! 🚀 Este é o seu primeiro projeto utilizando o Jetpack Compose na Fase 3: O Futuro é Agora (Declarativo).

Esqueça o desenho de telas clássico e engessado em arquivos XML! Com o Compose, nós desenhamos e programamos interfaces utilizando a mesma linguagem: o Kotlin. O desenvolvimento se torna incrivelmente mais rápido, limpo, reativo e visualmente espetacular.


✅ Pré-requisitos

Antes de começar, certifique-se de já ter estudado:


🎯 Objetivo do Projeto

Criar um cartão de identificação virtual de treinador Pokémon (Trainer ID Card) altamente premium, contendo:

graph TD
    A[Box - Noite Estelar Gradiente] --> B[Card - Cartão Branco Translúcido]
    B --> C[Column - Alinhamento Vertical]
    C --> D[Box - Avatar Circular com Borda Gradiente Neon]
    C --> E[Row - Nome ASH KETCHUM + Ícone Verificado]
    C --> F[Text - MESTRE POKÉMON]
    C --> G[HorizontalDivider]
    C --> H[Row - Stats: Rank S+ / Vitórias 1540]
    C --> I[Text - MEU TIME PRINCIPAL]
    C --> J[Row - Pokémons: Pikachu / Charizard / Blastoise]
    C --> K[Button - SOLICITAR BATALHA]

📖 Dicionário do Projeto


🛠️ Passo 1: Configurando o Projeto no Android Studio

Como este projeto marca a nossa entrada no desenvolvimento declarativo, a criação deve ser atenta:

  1. Abra o Android Studio e clique em New Project.
  2. Atenção Crítica: Selecione o template Empty Activity (O ícone tem o logotipo do Compose 🌌. Não selecione o “Empty Views Activity”).
  3. Configure as informações:
    • Name: Cartao Treinador Compose
    • Package Name: br.com.curso.cartaotreinador
    • Language: Kotlin (Obrigatório para Compose).
    • Minimum SDK: API 24 ou superior.
  4. Clique em Finish e espere o Gradle estruturar o projeto.

🖼️ Passo 2: Importando Recursos Visuais (Assets)

Precisamos dos avatares e figuras dos Pokémons importados nas pastas de recursos tradicionais:

  1. Consiga as imagens trainer_avatar.png, pikachu.png, charizard.png e blastoise.png (disponíveis na pasta app/src/main/res/drawable deste projeto).
  2. Cole todas elas na pasta do seu novo projeto: app > src > main > res > drawable.

🎨 Passo 3: Estruturando o Fundo e o Cartão Translúcido

No Compose, todo o design acontece diretamente no arquivo MainActivity.kt. Vamos começar apagando o código gerado automaticamente e criando nossa função principal com gradiente noturno.

  1. Abra MainActivity.kt.
  2. Adicione a função @Composable que desenhará a base do app:
@Composable
fun ModernTrainerCard() {
    // 1. Cria um belo gradiente vertical noturno para o fundo
    val backgroundGradient = Brush.verticalGradient(
        colors = listOf(Color(0xFF0F0C29), Color(0xFF302B63), Color(0xFF24243E))
    )

    // 2. Box cobrindo toda a tela com o gradiente
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(backgroundGradient),
        contentAlignment = Alignment.Center
    ) {
        // 3. Cartão centralizado branco levemente translúcido
        Card(
            modifier = Modifier
                .padding(20.dp)
                .fillMaxWidth()
                .shadow(elevation = 30.dp, shape = RoundedCornerShape(32.dp)),
            shape = RoundedCornerShape(32.dp),
            colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.95f))
        ) {
            Column(
                modifier = Modifier.padding(24.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                // Aqui ficarão os itens internos do cartão!
            }
        }
    }
}

🎨 Passo 4: Adicionando Detalhes do Perfil e Grid de Estatísticas

Dentro da nossa Column, vamos empilhar os dados do Treinador:

  1. Avatar com Borda Neon: Usamos uma Box circular preenchida com um gradiente linear para servir de borda brilhante de fundo para a imagem de perfil.
  2. Nome e Ícone: Usamos uma Row horizontal juntando o texto em negrito e o ícone de conta verificada.
  3. Grid de Stats: Construímos um componente modular reutilizável @Composable fun StatItem para exibir de forma elegante o Rank e as Vitórias.

🎨 Passo 5: Exibindo o Time Principal e Ações

Para renderizar os 3 Pokémons de forma alinhada e arredondada, criamos uma função @Composable fun PokemonIcon que renderiza imagens envoltas em círculos brancos com bordas suaves e sombras realistas. E finalizamos com o botão de ação na base da tela.


🛠️ Requisitos Críticos de Configuração

1. Chamada no setContent

Lembre-se que as funções @Composable só aparecem na tela do aparelho se forem invocadas dentro do bloco setContent da sua MainActivity:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ModernTrainerCard() // Seu cartão rodando aqui!
        }
    }
}

📸 Resultado Esperado

Veja como sua tela deve ficar ao final deste projeto:

Cartão de treinador finalizado (Ash Ketchum)


🏆 Desafios para você (Upgrade!)

Se você terminou de programar a tela base e tudo está funcionando, experimente estes upgrades:


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

Use o código Kotlin completo abaixo para validar sua implementação ou corrigir problemas de compilação na sua MainActivity.kt.

📄 Código Kotlin Completo (MainActivity.kt)

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

package br.com.curso.cartaotreinador

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.ThumbUp
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ModernTrainerCard()
        }
    }
}

@Composable
fun ModernTrainerCard() {
    // Gradiente de Fundo Premium (Noite Estelar)
    val backgroundGradient = Brush.verticalGradient(
        colors = listOf(Color(0xFF0F0C29), Color(0xFF302B63), Color(0xFF24243E))
    )

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(backgroundGradient),
        contentAlignment = Alignment.Center
    ) {
        Card(
            modifier = Modifier
                .padding(20.dp)
                .fillMaxWidth()
                .shadow(elevation = 30.dp, shape = RoundedCornerShape(32.dp)),
            shape = RoundedCornerShape(32.dp),
            colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.95f))
        ) {
            Column(
                modifier = Modifier.padding(24.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                // Avatar com Borda Gradiente Neon
                Box(
                    modifier = Modifier
                        .size(130.dp)
                        .clip(CircleShape)
                        .background(Brush.linearGradient(listOf(Color(0xFF00E676), Color(0xFF00B0FF))))
                        .padding(4.dp),
                    contentAlignment = Alignment.Center
                ) {
                    Image(
                        painter = painterResource(id = R.drawable.trainer_avatar),
                        contentDescription = "Avatar Treinador",
                        modifier = Modifier
                            .fillMaxSize()
                            .clip(CircleShape)
                            .border(2.dp, Color.White, CircleShape),
                        contentScale = ContentScale.Crop
                    )
                }

                Spacer(modifier = Modifier.height(16.dp))

                // Nome e Título
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Text(
                        text = "ASH KETCHUM",
                        fontSize = 26.sp,
                        fontWeight = FontWeight.Black,
                        color = Color(0xFF1A1A1A)
                    )
                    Spacer(modifier = Modifier.width(8.dp))
                    Icon(
                        Icons.Default.CheckCircle, 
                        contentDescription = "Verificado", 
                        tint = Color(0xFF00B0FF), 
                        modifier = Modifier.size(24.dp)
                    )
                }

                Text(
                    text = "MESTRE POKÉMON • PALLET TOWN",
                    fontSize = 12.sp,
                    color = Color.Gray,
                    fontWeight = FontWeight.Bold,
                    letterSpacing = 1.sp
                )

                HorizontalDivider(
                    modifier = Modifier.padding(vertical = 24.dp), 
                    color = Color.LightGray.copy(alpha = 0.4f)
                )

                // Stats do Treinador (Grid Horizontal)
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.SpaceEvenly
                ) {
                    StatItem(Icons.Default.Star, "Rank", "S+", Color(0xFFFFA000))
                    StatItem(Icons.Default.ThumbUp, "Vitórias", "1540", Color(0xFF1976D2))
                }

                Spacer(modifier = Modifier.height(24.dp))

                // Meus Pokémons Favoritos (Time)
                Text(
                    text = "MEU TIME PRINCIPAL",
                    fontSize = 14.sp,
                    fontWeight = FontWeight.ExtraBold,
                    color = Color(0xFF333333)
                )

                Spacer(modifier = Modifier.height(16.dp))

                // Alinhamento dos Ícones
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.Center
                ) {
                    PokemonIcon(R.drawable.pikachu, "Pikachu")
                    Spacer(modifier = Modifier.width(16.dp))
                    PokemonIcon(R.drawable.charizard, "Charizard")
                    Spacer(modifier = Modifier.width(16.dp))
                    PokemonIcon(R.drawable.blastoise, "Blastoise")
                }

                Spacer(modifier = Modifier.height(32.dp))

                // Botão de Ação
                Button(
                    onClick = { },
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(60.dp),
                    shape = RoundedCornerShape(16.dp),
                    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFD32F2F)),
                    elevation = ButtonDefaults.buttonElevation(defaultElevation = 8.dp)
                ) {
                    Text("SOLICITAR BATALHA", fontSize = 16.sp, fontWeight = FontWeight.Bold)
                }
            }
        }
    }
}

/**
 * Componente modular para itens estatísticos
 */
@Composable
fun StatItem(icon: ImageVector, label: String, value: String, tint: Color) {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Icon(icon, contentDescription = null, tint = tint, modifier = Modifier.size(30.dp))
        Text(text = value, fontSize = 20.sp, fontWeight = FontWeight.Black, color = Color.Black)
        Text(text = label, fontSize = 10.sp, color = Color.Gray, fontWeight = FontWeight.Bold)
    }
}

/**
 * Componente circular para os Pokemons com sombras
 */
@Composable
fun PokemonIcon(resId: Int, name: String) {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Box(
            modifier = Modifier
                .size(70.dp)
                .shadow(4.dp, CircleShape)
                .background(Color.White, CircleShape)
                .border(1.dp, Color.LightGray.copy(alpha = 0.5f), CircleShape)
                .padding(8.dp),
            contentAlignment = Alignment.Center
        ) {
            Image(
                painter = painterResource(id = resId),
                contentDescription = name,
                modifier = Modifier.fillMaxSize()
            )
        }
    }
}