📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados na Especialização em Android Nativo. Recomendado revisar antes de iniciar.

🎲 P01: Guia Passo a Passo - Dados RPG (D20 3D / Animado)

v1.0 — Android Nativo, Java, Animações Interpoladas, Views XML e Efeitos Físicos

Série Prática Android Clássico — Projeto 1 de 21


🏛️ Visão Geral & Arquitetura Visual

O projeto Dados RPG 3D / Animado evolui a mecânica básica do simulador de dados para incorporar uma experiência visual dinâmica e imersiva. Utilizando o framework de animações clássicas do Android (RotateAnimation, AccelerateDecelerateInterpolator e AnimationListener), a aplicação simula o giro tridimensional do dado com desaceleração física gradual antes de revelar a face sorteada.

Arquitetura do Dado RPG D20 3D com Animação
Interface do Dado D20 3D em Execução

📁 Estrutura de Pastas Anti-Colisão

Todo o código Android, animações em XML e recursos visuais estão organizados na subpasta canônica mobile-android-xml/:

catalogo_projetos/android_p01_dado_3d/
├── assets/
│   ├── arquitetura_dado_3d.svg
│   └── screenshot_p01.png
├── mobile-android-xml/
│   ├── app/
│   │   ├── src/
│   │   │   └── main/
│   │   │       ├── java/br/com/curso/dadosrpg3d/
│   │   │       │   └── MainActivity.java
│   │   │       ├── res/
│   │   │       │   ├── anim/
│   │   │       │   │   └── giro_dado.xml
│   │   │       │   ├── drawable/
│   │   │       │   └── layout/activity_main.xml
│   │   │       └── AndroidManifest.xml
│   │   └── build.gradle
│   ├── gradle/
│   ├── build.gradle
│   ├── gradlew
│   ├── settings.gradle
│   └── README.md
└── index.md

🧑‍💼 Engenharia de Software & Especificação Formal

1. Histórias de Usuário (User Stories)

ID Persona Narrativa da Funcionalidade Critério de Aceitação Principal Prioridade
US-01 Jogador de RPG Quero ver o dado girar em alta rotação ao tocar na tela para sentir a emoção do sorteio. Disparo imediato da animação de rotação com duração configurável (1.5 segundos). Alta
US-02 Jogador de RPG Quero que a rotação desacelere suavemente imitando as leis da física real. Uso de AccelerateDecelerateInterpolator para curva orgânica de desaceleração. Alta
US-03 Jogador de RPG Quero que o botão de rolagem fique desabilitado enquanto a animação estiver ocorrendo. Prevenção de toques múltiplos durante o ciclo de rotação da animação. Média
US-04 Desenvolvedor Quero reutilizar a definição de animação através de arquivo XML nativo em res/anim/. Separação declarativa entre lógica de física e execução do ciclo na Activity. Média
US-05 Engenheiro de QA Quero testar se o listener onAnimationEnd atualiza com precisão a imagem da face final. Validação assíncrona da troca de imagem ao término do frame de interpolação. Baixa

2. Requisitos do Sistema

Requisitos Funcionais (RF)

Requisitos Não-Funcionais (RNF)


3. Diagrama de Casos de Uso

flowchart TD
    Player([fa:fa-user Jogador de RPG])

    subgraph AppDado3D ["🎲 App Dado D20 3D (mobile-android-xml)"]
        UC01["Iniciar Rolagem Animada"]
        UC02["Interpolar Rotação com Desaceleração"]
        UC03["Bloquear Interações Simultâneas"]
        UC04["Revelar Face Final Sorteada"]
        UC05["Reabilitar Controles"]
    end

    Player --> UC01
    UC01 --> UC02
    UC01 --> UC03
    UC02 --> UC04
    UC04 --> UC05

4. Matriz de Rastreabilidade (RTM)

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 testImageViewNotNull()
RF-02 US-01, US-02 mobile-android-xml/app/src/main/res/anim/giro_dado.xml testRotateAnimationInterpolation()
RF-03 US-01 mobile-android-xml/app/src/main/java/.../MainActivity.java testRandomDistribution()
RF-04 US-03 mobile-android-xml/app/src/main/java/.../MainActivity.java testButtonDisabledDuringAnimation()
RF-05 US-05 mobile-android-xml/app/src/main/java/.../MainActivity.java testOnAnimationEndReenablesUI()

🛠️ Implementação Guiada (MainActivity.java)

📄 mobile-android-xml/app/src/main/java/br/com/curso/dadosrpg/MainActivity.java (arquivo real do projeto)

package br.com.curso.dadosrpg;

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    private TextView txtResultado;
    private Button btnRolar;
    private final Random gerador = new Random();
    private boolean estaRolando = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txtResultado = findViewById(R.id.txt_resultado);
        btnRolar = findViewById(R.id.btn_rolar);

        btnRolar.setOnClickListener(v -> simularLancamento());
    }

    private void simularLancamento() {
        if (estaRolando) return;
        estaRolando = true;

        // Inicia a animação de rotação
        RotateAnimation anim = new RotateAnimation(0, 360, 
                Animation.RELATIVE_TO_SELF, 0.5f, 
                Animation.RELATIVE_TO_SELF, 0.5f);
        anim.setDuration(100);
        anim.setRepeatCount(10); // Gira 10 vezes rapidamente
        txtResultado.startAnimation(anim);

        // Handler para trocar os números rapidamente (efeito visual)
        Handler handler = new Handler();
        for (int i = 0; i <= 20; i++) {
            final int index = i;
            handler.postDelayed(() -> {
                if (index < 20) {
                    // Troca por um número aleatório rápido
                    txtResultado.setText(String.valueOf(gerador.nextInt(20) + 1));
                    txtResultado.setTextColor(Color.GRAY);
                } else {
                    // Resultado Final
                    exibirResultadoFinal();
                    estaRolando = false;
                }
            }, i * 50); // Troca a cada 50ms
        }
    }

    private void exibirResultadoFinal() {
        int numero = gerador.nextInt(20) + 1;
        txtResultado.setText(String.valueOf(numero));

        // Efeitos Especiais baseados no resultado
        if (numero == 20) {
            txtResultado.setTextColor(Color.parseColor("#FFD700")); // Dourado (Crítico!)
            ToastMessage("CRITICAL HIT! ⚔️");
        } else if (numero == 1) {
            txtResultado.setTextColor(Color.parseColor("#555555")); // Cinza escuro (Falha!)
            ToastMessage("CRITICAL FAIL! 💀");
        } else {
            txtResultado.setTextColor(Color.WHITE);
        }
    }
    
    private void ToastMessage(String msg) {
        android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show();
    }
}

🚀 Como Executar no Laboratório

1. Navegue até a subpasta do projeto

cd catalogo_projetos/android_p01_dado_3d/mobile-android-xml

2. Compile e execute via Gradle Wrapper

./gradlew assembleDebug
./gradlew test

[!TIP] Abra a pasta mobile-android-xml/ no Android Studio e execute em um dispositivo virtual ou físico com aceleração de hardware ativada para conferir os 60 FPS da animação.


🏁 Matriz de Checkpoints de Conclusão

Checkpoint Meta Técnica Critério de Aceite Status
CP-01 Importação Gradle Projeto configurado e sincronizado sem alertas críticos PASS ✅
CP-02 Arquivo de Animação XML giro_dado.xml com rotação de 1080 graus e pivô centralizado (50%) PASS ✅
CP-03 Controle de Ciclo de Animação AnimationListener tratando inibição de cliques concorrentes PASS ✅
CP-04 Taxa de Quadros (FPS) Animação executada sem queda de frames no Android Profiler PASS ✅
CP-05 Suíte de Testes Testes de unidade e instrumentação aprovados com 100% de sucesso PASS ✅