🧠 P02: Jogo da Memória Gamer (Guia Completo)
Neste projeto, você vai subir de nível! O desafio é criar um Jogo da Memória Gamer clássico com 8 cartas (4 pares). Você aprenderá a lidar com lógica de comparação de estados, manipulação dinâmica de imagens e controle de tempo com atraso programado.
✅ Pré-requisitos
Antes de começar, certifique-se de já ter estudado:
- 📘 Cap 01: Seu Primeiro App (Activity, XML e Views)
- 📘 Cap 02: Sorte e Decisão em Java (Variáveis, Operadores e Random)
- 🏗️ Projeto anterior: P02: Jogo da Memória POC
🎯 Objetivo do Jogo
Criar uma grade de 4 linhas por 2 colunas com cartas viradas para baixo (mostrando o verso da carta). O jogador deve clicar em duas cartas:
- Se forem iguais, as cartas são travadas abertas e o jogador ganha um ponto.
- Se forem diferentes, as cartas revelam suas figuras temporariamente e voltam a ficar viradas para baixo após 0.8 segundos (
800ms), permitindo que o jogador tente memorizá-las. - Quando os 4 pares forem encontrados, o jogo exibe uma mensagem triunfal de vitória!
graph TD
A[Início / Embaralhar] --> B[Clique na Carta 1]
B --> C[Revelar Carta 1]
C --> D[Clique na Carta 2]
D --> E[Revelar Carta 2]
E --> F{As cartas são iguais?}
F -- Sim --> G[Travar as cartas & Somar Ponto]
F -- Não --> H[Aguardar 800ms & Ocultar cartas]
G --> I{Todos os 4 pares encontrados?}
H --> B
I -- Sim --> J[🏆 Vitória Gamer!]
I -- Não --> B
📥 Downloads de Assets
Para que seu jogo fique com um visual gamer incrível, disponibilizamos um pacote de imagens prontas (Pokémons e Verso da Carta):
Instruções de Instalação:
- Baixe o arquivo
.zipacima. - Extraia o arquivo no seu computador.
- No Android Studio, copie todas as imagens extraídas e cole-as dentro da pasta
app > src > main > res > drawable. - Atenção: Certifique-se de que os arquivos mantiveram os nomes originais em minúsculo e sem espaços:
card_back.png,poke_pikachu.png,poke_charmander.png,poke_squirtle.pngepoke_bulbasaur.png.
📖 Dicionário do Projeto
- GridLayout: Um tipo de layout que organiza os componentes da tela em formato de grade ou tabela (linhas e colunas). Ideal para tabuleiros.
- Handler: Uma classe do Android que nos permite enviar e processar mensagens ou executar blocos de código com agendamento de tempo (delays).
- postDelayed: Método do
Handlerusado para adiar a execução de uma ação por uma quantidade específica de milissegundos. - setTag / getTag: Mecanismo que permite anexar uma etiqueta invisível (como uma String contendo o nome do personagem) a um botão da tela. Usaremos isso para comparar se as cartas são iguais sem precisar ler imagens diretamente.
- setImageResource: Método Java para trocar dinamicamente a imagem que está sendo exibida dentro de um
ImageButton.
🛠️ Passo 1: Configurando o Projeto no Android Studio
- Abra o Android Studio e clique em
New Project. - Escolha o template Empty Views Activity.
- Configure as informações do projeto:
- Name:
Memoria Gamer TecPro - Package Name:
br.com.curso.memoria - Language: Java (O clássico).
- Minimum SDK: API 24 (Android 7.0) ou superior.
- Name:
- Clique em Finish e aguarde a sincronização inicial do Gradle.
🎨 Passo 2: Desenhando o Tabuleiro (XML)
Nossa interface usará um elegante tema escuro gamer (#1A1A1A), textos em ciano neon (#03DAC5) e uma grade centralizada de cartas.
- Abra o arquivo
app > src > main > res > layout > activity_main.xml. - Substitua o conteúdo pelo código XML a seguir:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="8dp"
android:background="#1A1A1A">
<!-- Título Gamer -->
<TextView
android:id="@+id/txt_titulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MEMÓRIA GAMER"
android:textColor="#03DAC5"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="4dp" />
<!-- Texto de Status das Ações -->
<TextView
android:id="@+id/txt_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Encontre os pares!"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:layout_marginBottom="12dp" />
<!-- Grade 4x2 para as 8 cartas -->
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="2"
android:rowCount="4"
android:layout_marginBottom="20dp">
<ImageButton android:id="@+id/card1" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card2" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card3" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card4" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card5" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card6" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card7" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card8" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
</GridLayout>
<!-- Botão de Reiniciar -->
<Button
android:id="@+id/btn_reiniciar"
android:layout_width="180dp"
android:layout_height="50dp"
android:text="REINICIAR JOGO"
android:backgroundTint="#03DAC5"
android:textColor="#1A1A1A"
android:textStyle="bold" />
</LinearLayout>
🧠 Passo 3: Programando as Regras e o Jogo (Java)
Vamos implementar toda a mecânica. A estrutura de código funciona assim:
- Declaração de Listas: Criamos uma lista de
ImageButtonpara as cartas da tela e uma lista deString(tags) para armazenar a identidade de cada carta. - Inicialização e Embaralhamento: Ligamos os IDs do XML, adicionamos os nomes dos Pokémons às tags (4 pares) e embaralhamos as tags usando
Collections.shuffle(tags). - Lógica do Clique:
- Revela a carta clicada baseado na tag que ela recebeu.
- Se for o primeiro clique, armazena a referência.
- Se for o segundo clique, compara as duas tags.
- Se iguais: Trava-as desabilitadas (
setEnabled(false)) e soma1aos pares encontrados. - Se diferentes: Bloqueia cliques temporários (
aguardando = true), agenda uma volta ao verso das cartas após 800ms viaHandlere destrava a tela.
Abra app > src > main > java > br > com > curso > memoria > MainActivity.java e digite/cole a estrutura lógica completa disponível no Gabarito ao final.
🛠️ Requisitos Críticos de Configuração
1. Flag do AndroidX
Para que a compatibilidade de componentes modernos funcione perfeitamente, confirme se o arquivo gradle.properties na raiz do seu projeto Android possui estas duas propriedades essenciais habilitadas:
android.useAndroidX=true
android.enableJetifier=true
2. Formato de Dimensões em XML (O erro do ponto)
Ao desenhar layouts no Android, tamanhos de margem, espaçamento e dimensões (dp/sp) não aceitam pontos decimais.
- ❌ Incorreto:
android:layout_margin="4.5dp" - ✅ Correto:
android:layout_margin="4dp"ouandroid:layout_margin="5dp"
📸 Resultado Esperado
Veja como sua tela deve ficar ao final deste projeto:

🏆 Desafios para você (Upgrade!)
Se você terminou de programar a mecânica base do jogo e tudo está funcionando, experimente estes upgrades:
- Contador de Cliques/Tentativas: Adicione um novo
TextViewna tela para mostrar quantas jogadas o usuário levou para vencer. - Sons de Feedback: Reproduza um som curto de sucesso quando o usuário acertar um par, e um som de erro quando falhar.
📖 Gabarito Oficial de Código (Para Conferência)
Use os códigos completos abaixo para validar sua implementação ou corrigir problemas de compilação.
📄 Layout Completo (activity_main.xml)
Disponível em: app/src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="8dp"
android:background="#1A1A1A">
<TextView
android:id="@+id/txt_titulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MEMÓRIA GAMER"
android:textColor="#03DAC5"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="4dp" />
<TextView
android:id="@+id/txt_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Encontre os pares!"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:layout_marginBottom="12dp" />
<!-- Grade 4x2 para 8 cartas -->
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="2"
android:rowCount="4"
android:layout_marginBottom="20dp">
<ImageButton android:id="@+id/card1" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card2" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card3" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card4" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card5" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card6" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card7" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
<ImageButton android:id="@+id/card8" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:background="@android:color/transparent" android:scaleType="fitCenter" />
</GridLayout>
<Button
android:id="@+id/btn_reiniciar"
android:layout_width="180dp"
android:layout_height="50dp"
android:text="REINICIAR JOGO"
android:backgroundTint="#03DAC5"
android:textColor="#1A1A1A" />
</LinearLayout>
📄 Lógica Java Completa (MainActivity.java)
Localizado em: app/src/main/java/br/com/curso/memoria/MainActivity.java
package br.com.curso.memoria;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MainActivity extends AppCompatActivity {
// Controles de estado das seleções de cartas
private ImageButton primeiraCarta = null;
private ImageButton segundaCarta = null;
private boolean aguardando = false; // Bloqueia cliques rápidos durante o delay
private int paresEncontrados = 0;
private TextView txtStatus;
// Listas para gerenciar dinamicamente as referências de tela e valores
private final List<ImageButton> cards = new ArrayList<>();
private final List<String> tags = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Vinculando componentes de texto e ações
txtStatus = findViewById(R.id.txt_status);
Button btnReiniciar = findViewById(R.id.btn_reiniciar);
btnReiniciar.setOnClickListener(v -> reiniciarJogo());
// 1. Inicializando as referências das 8 cartas do tabuleiro
cards.add(findViewById(R.id.card1));
cards.add(findViewById(R.id.card2));
cards.add(findViewById(R.id.card3));
cards.add(findViewById(R.id.card4));
cards.add(findViewById(R.id.card5));
cards.add(findViewById(R.id.card6));
cards.add(findViewById(R.id.card7));
cards.add(findViewById(R.id.card8));
// 2. Definindo a lista de figuras (4 pares = 8 elementos idênticos às imagens em drawable)
tags.add("poke_pikachu");
tags.add("poke_charmander");
tags.add("poke_squirtle");
tags.add("poke_bulbasaur");
tags.add("poke_pikachu");
tags.add("poke_charmander");
tags.add("poke_squirtle");
tags.add("poke_bulbasaur");
// Dá o pontapé inicial no jogo
iniciarJogo();
}
/**
* Reseta as pontuações, embaralha o tabuleiro e redefine as cartas
*/
private void iniciarJogo() {
paresEncontrados = 0;
txtStatus.setText("Encontre os pares!");
// Embaralha as figuras aleatoriamente
Collections.shuffle(tags);
// Configura cada ImageButton individualmente
for (int i = 0; i < cards.size(); i++) {
ImageButton card = cards.get(i);
// Atribui a identidade secreta (tag) para a carta
card.setTag(tags.get(i));
card.setEnabled(true); // Garante que a carta possa ser clicada
card.setImageResource(R.drawable.card_back); // Mostra o verso
// Configura o ouvinte de clique
card.setOnClickListener(v -> aoClicarNaCarta(card));
}
}
/**
* Executa a regra ao clicar em uma carta
*/
private void aoClicarNaCarta(ImageButton carta) {
// Ignora cliques se o jogo estiver aguardando o delay de erro
// ou se o usuário clicar duas vezes seguidas na mesma carta
if (aguardando || carta == primeiraCarta) return;
revelarCarta(carta);
if (primeiraCarta == null) {
// Primeiro clique da jogada
primeiraCarta = carta;
} else {
// Segundo clique da jogada: realiza a verificação
segundaCarta = carta;
verificarPar();
}
}
/**
* Mudar a imagem do ImageButton para revelar o personagem baseado na tag
*/
private void revelarCarta(ImageButton carta) {
String tag = (String) carta.getTag();
switch (tag) {
case "poke_pikachu":
carta.setImageResource(R.drawable.poke_pikachu);
break;
case "poke_charmander":
carta.setImageResource(R.drawable.poke_charmander);
break;
case "poke_squirtle":
carta.setImageResource(R.drawable.poke_squirtle);
break;
case "poke_bulbasaur":
carta.setImageResource(R.drawable.poke_bulbasaur);
break;
}
}
/**
* Verifica se os dois cards revelados são um par idêntico
*/
private void verificarPar() {
if (primeiraCarta.getTag().equals(segundaCarta.getTag())) {
// SUCESSO: Par encontrado!
paresEncontrados++;
// Trava as cartas para não receberem mais cliques
primeiraCarta.setEnabled(false);
segundaCarta.setEnabled(false);
Toast.makeText(this, "Par encontrado! 🎉", Toast.LENGTH_SHORT).show();
resetarSelecao();
verificarVitoria();
} else {
// ERRO: Cartas diferentes. Aguarda 800ms antes de virá-las de volta
aguardando = true;
new Handler().postDelayed(() -> {
// Vira de volta para o verso
primeiraCarta.setImageResource(R.drawable.card_back);
segundaCarta.setImageResource(R.drawable.card_back);
resetarSelecao();
aguardando = false; // Libera novos cliques
}, 800);
}
}
/**
* Valida se todos os pares foram achados
*/
private void verificarVitoria() {
if (paresEncontrados == 4) {
txtStatus.setText("🏆 VOCÊ É UM MESTRE!");
Toast.makeText(this, "Incrível! Você encontrou todos os pares! 🎮", Toast.LENGTH_LONG).show();
}
}
/**
* Limpa as referências das cartas selecionadas na rodada
*/
private void resetarSelecao() {
primeiraCarta = null;
segundaCarta = null;
}
/**
* Reinicia a partida do zero
*/
private void reiniciarJogo() {
resetarSelecao();
aguardando = false;
iniciarJogo();
}
}