🎨 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:
- 📘 Cap 07: Perfil de Jogador (Compose UI)
- 📘 Cap 08: Dashboard Gamer (Layouts e Pastas)
- 🏗️ Projeto anterior: P03: Genius Game (Simon Says)
🎯 Objetivo do Projeto
Criar um cartão de identificação virtual de treinador Pokémon (Trainer ID Card) altamente premium, contendo:
- Fundo imersivo com gradiente de noite estelar.
- Avatar circular com borda brilhante em gradiente neon.
- Nome e status verificado com ícones integrados.
- Painel centralizado com grid de estatísticas do treinador (Rank e número de vitórias).
- Visualização horizontal do time principal de Pokémons favoritos, envoltos em cards circulares sombreados.
- Um belo botão de ação para “Solicitar Batalha”.
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
- Jetpack Compose: O kit de ferramentas modernas do Android para construir interfaces de forma declarativa (descrevemos como a tela deve parecer de acordo com os dados, e o Android cuida de desenhá-la).
- Composable (
@Composable): Funções especiais anotadas no Kotlin que servem como blocos de construção para desenhar componentes na tela. - Box: Um contêiner básico que permite empilhar elementos uns em cima dos outros (eixo Z). Perfeito para aplicar fundos graduados ou sobrepor decorações.
- Column: Organizador linear vertical. Tudo o que for inserido dentro dele será empilhado um embaixo do outro.
- Row: Organizador linear horizontal. Tudo o que for inserido dentro dele será empilhado um ao lado do outro.
- Card: Caixa com cantos arredondados, preenchimento de cor e sombras internas que destaca um conteúdo visual na tela.
- Modifier: O construtor dinâmico de estilo do Compose. Usamos ele para alterar padding, largura, altura, sombras, bordas, cliques e formatos dos componentes.
- Brush: Ferramenta usada para pintar gradientes complexos (lineares, verticais ou radiais) na tela.
🛠️ Passo 1: Configurando o Projeto no Android Studio
Como este projeto marca a nossa entrada no desenvolvimento declarativo, a criação deve ser atenta:
- Abra o Android Studio e clique em
New Project. - Atenção Crítica: Selecione o template Empty Activity (O ícone tem o logotipo do Compose 🌌. Não selecione o “Empty Views Activity”).
- 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.
- Name:
- 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:
- Consiga as imagens
trainer_avatar.png,pikachu.png,charizard.pngeblastoise.png(disponíveis na pastaapp/src/main/res/drawabledeste projeto). - 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.
- Abra
MainActivity.kt. - Adicione a função
@Composableque 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:
- Avatar com Borda Neon: Usamos uma
Boxcircular preenchida com um gradiente linear para servir de borda brilhante de fundo para a imagem de perfil. - Nome e Ícone: Usamos uma
Rowhorizontal juntando o texto em negrito e o ícone de conta verificada. - Grid de Stats: Construímos um componente modular reutilizável
@Composable fun StatItempara 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:

🏆 Desafios para você (Upgrade!)
Se você terminou de programar a tela base e tudo está funcionando, experimente estes upgrades:
- Botão de Tipo Elemental: Adicione um botão que, ao ser clicado, alterna a cor de fundo do cartão simulando outro tipo elemental.
- Troca de Treinador: Use
remember/varpara alternar entre 2 conjuntos de dados de treinador ao clicar em um botão “Próximo”.
📖 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()
)
}
}
}