📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.
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.
Antes de começar, certifique-se de já ter estudado:
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:
800ms), permitindo que o jogador tente memorizá-las.💡 O mesmo conceito em outra stack: este é o clássico Jogo da Memória — o mesmo desafio (comparar estados, temporizar) também aparece em Java Swing puro no curso Sistemas com Java.
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
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:
.zip acima.app > src > main > res > drawable.card_back.png, poke_pikachu.png, poke_charmander.png, poke_squirtle.png e poke_bulbasaur.png.Handler usado para adiar a execução de uma ação por uma quantidade específica de milissegundos.ImageButton.New Project.Memoria Gamer TecProbr.com.curso.memoriaNossa interface usará um elegante tema escuro gamer (#1A1A1A), textos em ciano neon (#03DAC5) e uma grade centralizada de cartas.
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">
<!-- 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>
Vamos implementar toda a mecânica. A estrutura de código funciona assim:
ImageButton para as cartas da tela e uma lista de String (tags) para armazenar a identidade de cada carta.Collections.shuffle(tags).setEnabled(false)) e soma 1 aos pares encontrados.aguardando = true), agenda uma volta ao verso das cartas após 800ms via Handler e 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.
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
Ao desenhar layouts no Android, tamanhos de margem, espaçamento e dimensões (dp/sp) não aceitam pontos decimais.
android:layout_margin="4.5dp"android:layout_margin="4dp" ou android:layout_margin="5dp"Veja como sua tela deve ficar ao final deste projeto:

Se você terminou de programar a mecânica base do jogo e tudo está funcionando, experimente estes upgrades:
TextView na tela para mostrar quantas jogadas o usuário levou para vencer.Use os códigos completos abaixo para validar sua implementação ou corrigir problemas de compilação.
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>
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();
}
}
—`
No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd android_p02_memoria_png
./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, basta navegar até a pasta antes de executar: cd proj_aplicacoes_full_stack/projetos/android_p02_memoria_png