📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados na Especialização em Android Nativo. Recomendado revisar antes de iniciar.
v1.0 — Android Nativo, Java, GridLayout, ImageButton, Temporizadores com Handler e Lógica de Pares
Série Prática Android Clássico — Projeto 2 de 21
- P01: Dados RPG (2D, 3D & Ciclo de Vida)
- ➡️ P02 (este): Jogo da Memória com Cartas PNG · Java · GridLayout
- P02 (POC): Jogo da Memória POC · Validação de Matriz
- P03: Genius / Simon Says Clássico & Demo de Áudio
O Jogo da Memória Android desafia o raciocínio e a agilidade do jogador em uma grade de cartas embaralhadas aleatoriamente. O projeto aprofunda a manipulação de layouts matriciais (GridLayout), a troca dinâmica de imagens em ImageButton com estados de carta (verso e frente), e a orquestração assíncrona de temporizadores de desviramento através da classe android.os.Handler.
Todos os recursos gráficos, sons e código Java residem na subpasta canônica mobile-android-xml/:
catalogo_projetos/android_p02_memoria_png/
├── assets/
│ ├── arquitetura_memoria_png.svg
│ ├── screenshot.png
│ ├── screenshot_8cards.png
│ └── screenshot_classic.png
├── mobile-android-xml/
│ ├── app/
│ │ ├── src/
│ │ │ └── main/
│ │ │ ├── java/br/com/curso/jogomemoria/
│ │ │ │ └── MainActivity.java
│ │ │ ├── res/
│ │ │ │ ├── drawable/
│ │ │ │ └── layout/activity_main.xml
│ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle/
│ ├── build.gradle
│ ├── gradlew
│ ├── settings.gradle
│ └── README.md
└── index.md
| ID | Persona | Narrativa da Funcionalidade | Critério de Aceitação Principal | Prioridade |
|---|---|---|---|---|
| US-01 | Jogador | Quero tocar em uma carta para que ela vire e mostre sua face ilustrada. | Abertura imediata da carta com feedback tátil e sonoro. | Alta |
| US-02 | Jogador | Quero que duas cartas iguais permaneçam abertas e contem como um par descoberto. | Fixação das cartas no tabuleiro e incremento do contador de pares encontrados. | Alta |
| US-03 | Jogador | Quero que duas cartas diferentes voltem a ficar viradas para baixo após 1 segundo. | Ação automática via Handler sem permitir toques intermediários indevidos. | Alta |
| US-04 | Jogador | Quero ver meu tempo de jogo e número de tentativas para tentar bater recordes. | Exibição de cronômetro contínuo e contador de movimentos na barra de status. | Média |
| US-05 | Desenvolvedor | Quero que a distribuição de cartas mude a cada reinício de partida. | Embaralhamento pseudo-aleatório através de Collections.shuffle(). |
Baixa |
GridLayout.Handler.postDelayed.res/drawable evitando estouro de Heap (OutOfMemory).flowchart TD
Player([fa:fa-user Jogador])
subgraph AppMemoria ["🃏 Jogo da Memória (mobile-android-xml)"]
UC01["Iniciar Partida / Embaralhar"]
UC02["Virar Primeira Carta"]
UC03["Virar Segunda Carta"]
UC04["Avaliar Correspondência de Par"]
UC05["Finalizar Partida e Exibir Estatísticas"]
end
Player --> UC01
Player --> UC02
UC02 --> UC03
UC03 --> UC04
UC04 -->|Par Correto| UC05
| ID Requisito | História de Usuário | Artefato de Código | Teste de Aceitação |
|---|---|---|---|
| RF-01 | US-01 | mobile-android-xml/app/src/main/res/layout/activity_main.xml |
testGridLayoutDimensions() |
| RF-02 | US-05 | mobile-android-xml/app/.../MainActivity.java |
testDeckShuffleContains8Pairs() |
| RF-03 | US-01, US-02 | mobile-android-xml/app/.../MainActivity.java |
testCardFlipStateMachine() |
| RF-04 | US-03 | mobile-android-xml/app/.../MainActivity.java |
testHandlerDelay1000ms() |
| RF-05 | US-04 | mobile-android-xml/app/.../MainActivity.java |
testVictoryDialogTrigger() |
MainActivity.java)📄 mobile-android-xml/app/src/main/java/br/com/curso/memoria/MainActivity.java (arquivo real do projeto)
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 {
private ImageButton primeiraCarta = null;
private ImageButton segundaCarta = null;
private boolean aguardando = false;
private int paresEncontrados = 0;
private TextView txtStatus;
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);
txtStatus = findViewById(R.id.txt_status);
Button btnReiniciar = findViewById(R.id.btn_reiniciar);
btnReiniciar.setOnClickListener(v -> reiniciarJogo());
// Inicializa as referências das 8 cartas
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));
// Inicializa as tags (4 pares = 8 cartas)
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");
iniciarJogo();
}
private void iniciarJogo() {
paresEncontrados = 0;
txtStatus.setText("Encontre os pares!");
Collections.shuffle(tags);
for (int i = 0; i < cards.size(); i++) {
ImageButton card = cards.get(i);
card.setTag(tags.get(i));
card.setEnabled(true);
card.setImageResource(R.drawable.card_back);
card.setOnClickListener(v -> aoClicarNaCarta(card));
}
}
// … (+63 linhas: veja o arquivo completo no projeto)
cd catalogo_projetos/android_p02_memoria_png/mobile-android-xml
./gradlew assembleDebug
./gradlew test
| Checkpoint | Meta Técnica | Critério de Aceite | Status |
|---|---|---|---|
| CP-01 | Distribuição da Matriz | Grade GridLayout 4x4 preenchida com cartas proporcionais |
PASS ✅ |
| CP-02 | Embaralhamento Aleatório | Posições distintas a cada nova partida via Collections.shuffle |
PASS ✅ |
| CP-03 | Controle Assíncrono com Handler | Desviramento temporizado sem travamento da thread principal | PASS ✅ |
| CP-04 | Prevenção de Toques Múltiplos | Flag booleana inibindo cliques concorrentes | PASS ✅ |
| CP-05 | Suíte de Testes Automatizados | Testes de unidade aprovados com 100% de sucesso | PASS ✅ |