📚 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, Animações Interpoladas, Views XML e Efeitos Físicos
Série Prática Android Clássico — Projeto 1 de 21
- P01 (2D): Dados RPG 2D Clássico · Java · XML Views · Eventos de Toque
- ➡️ P01 (3D / este): Dados RPG 3D · RotateAnimation & Interpoladores
- P01 (Hello World): Ciclo de Vida & ViewBinding em Kotlin
- P02: Jogo da Memória com Cartas PNG & Grid
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.
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
| 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 |
ImageView com ilustração do dado D20 e botão acionador de rolagem.AnimationListener.onAnimationStart().onAnimationEnd().Animation para evitar disparos frequentes do Garbage Collector.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
| 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() |
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();
}
}
cd catalogo_projetos/android_p01_dado_3d/mobile-android-xml
./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.
| 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 ✅ |