📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.
Bem-vindo à versão avançada e animada da sua primeira missão! 🚀 Neste projeto de entrada da Fase 1: Fundamentos Legados, vamos construir um Simulador de Dados D20 com Animação de Giro e Efeitos Visuais.
Em vez de apenas exibir um número estático instantaneamente, o dado vai girar na tela e trocar de números rapidamente (efeito “rolling”) antes de revelar o resultado. E caso você tire um 20 ou um 1, o app exibirá efeitos especiais de Acerto Crítico e Falha Crítica!
Antes de começar, certifique-se de já ter estudado:
Criar um aplicativo imersivo com interface premium de tema escuro e elementos centralizados. Ao clicar no botão vermelho “Lançar Dado”, o dado gira 360 graus consecutivamente e altera seus valores aleatoriamente em frações de segundos para simular a rolagem realista, revelando o resultado com formatação especial para acerto crítico e falha crítica.
graph TD
A[Início / App Aberto] --> B[Clique no Botão Rolar]
B --> C{Já está rolando?}
C -- Sim --> D[Ignorar clique extra]
C -- Não --> E[Travar cliques & Iniciar Animação]
E --> F[Girar D20 com RotateAnimation]
F --> G[Enfileirar 20 números rápidos com Handler]
G --> H[Exibir Resultado Final]
H --> I{Qual o resultado?}
I -- 20 --> J[Cor Dourada & Toast: CRITICAL HIT! ⚔️]
I -- 1 --> K[Cor Cinza & Toast: CRITICAL FAIL! 💀]
I -- Outro --> L[Cor Branca padrão]
J --> M[Destravar novos cliques]
K --> M
L --> M
#FFD700) diretamente em uma cor aplicável na tela.Configure o ambiente de forma idêntica à versão clássica:
New Project.Dados RPG 3Dbr.com.curso.dadosrpgPara esta versão, precisamos de uma bela imagem do dado de 20 faces para servir de fundo para os números:
d20_dice.png já presente na pasta res/drawable deste projeto).app > src > main > res > drawable.
d20_dice.png).Desta vez, criaremos uma interface premium com tema escuro e elementos centralizados.
app > src > main > res > layout > activity_main.xml.<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0F0F0F"
android:padding="24dp">
<!-- Header -->
<TextView
android:id="@+id/txt_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:text="RPG DICE MASTER"
android:textColor="#FF4444"
android:textSize="18sp"
android:letterSpacing="0.4"
android:textStyle="bold" />
<!-- Container do Dado Animado (Sobrepõe Imagem e Texto) -->
<FrameLayout
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_centerInParent="true">
<!-- Imagem do D20 no fundo -->
<ImageView
android:id="@+id/img_dado"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/d20_dice"
android:alpha="0.8"
android:scaleType="fitCenter" />
<!-- Número centralizado em cima do D20 -->
<TextView
android:id="@+id/txt_resultado"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="20"
android:textColor="#FFFFFF"
android:textSize="80sp"
android:textStyle="bold"
android:shadowColor="#FF0000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="15" />
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_header"
android:layout_centerHorizontal="true"
android:layout_marginTop="300dp"
android:text="TOQUE PARA ROLAR"
android:textColor="#555555"
android:textSize="14sp" />
<!-- Botão de Ação -->
<Button
android:id="@+id/btn_rolar"
android:layout_width="match_parent"
android:layout_height="65dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="30dp"
android:backgroundTint="#D32F2F"
android:text="LANÇAR DADO"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
Vamos codificar a lógica do lançamento. Faremos o dado rodar e exibir números temporários em altíssima velocidade antes de parar no número final.
MainActivity.java (em app > src > main > java > br.com.curso.dadosrpg > MainActivity.java).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);
// Uso de Expressão Lambda (Sintaxe Moderna)
btnRolar.setOnClickListener(v -> simularLancamento());
}
private void simularLancamento() {
// Evita múltiplos cliques simultâneos enquanto o dado roda
if (estaRolando) return;
estaRolando = true;
// 1. Cria a animação de rotação: Gira 360 graus em torno do próprio centro (0.5f)
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 consecutivas
txtResultado.startAnimation(anim);
// 2. Handler para alterar os números muito rápido (simula rolagem realista)
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 (cinza para indicar que está rolando)
txtResultado.setText(String.valueOf(gerador.nextInt(20) + 1));
txtResultado.setTextColor(Color.GRAY);
} else {
// Resultado Final do Sorteio
exibirResultadoFinal();
estaRolando = false;
}
}, i * 50); // Troca de número a cada 50 milissegundos
}
}
private void exibirResultadoFinal() {
int numero = gerador.nextInt(20) + 1;
txtResultado.setText(String.valueOf(numero));
// 3. Efeitos Especiais de RPG baseados no resultado sorteado
if (numero == 20) {
// Acerto Crítico: Cor Dourada e Toast vibrante!
txtResultado.setTextColor(Color.parseColor("#FFD700"));
ToastMessage("CRITICAL HIT! ⚔️");
} else if (numero == 1) {
// Falha Crítica: Cor Cinza Escura e Toast de falha!
txtResultado.setTextColor(Color.parseColor("#555555"));
ToastMessage("CRITICAL FAIL! 💀");
} else {
// Sucesso Padrão: Cor Branca
txtResultado.setTextColor(Color.WHITE);
}
}
private void ToastMessage(String msg) {
android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show();
}
}
Certifique-se de que o arquivo gradle.properties na raiz do seu projeto possui estas flags essenciais de compatibilidade ativas:
android.useAndroidX=true
android.enableJetifier=true
Diferente da programação convencional, medidas de dimensões no Android no XML não toleram números flutuantes:
android:padding="24.5dp"android:padding="24dp"Veja como sua tela deve ficar ao final deste projeto:

Terminou rápido? Deixe o app ainda mais incrível com estes desafios:
.mp3 de dados rolando na pasta res/raw e toque o som usando a classe MediaPlayer do Android assim que a simulação iniciar.SensorManager) para permitir que o usuário role os dados simplesmente balançando o aparelho!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"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0F0F0F"
android:padding="24dp">
<!-- Header -->
<TextView
android:id="@+id/txt_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:text="RPG DICE MASTER"
android:textColor="#FF4444"
android:textSize="18sp"
android:letterSpacing="0.4"
android:textStyle="bold" />
<!-- Container do Dado Animado -->
<FrameLayout
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_centerInParent="true">
<!-- Imagem do D20 no fundo -->
<ImageView
android:id="@+id/img_dado"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/d20_dice"
android:alpha="0.8"
android:scaleType="fitCenter" />
<!-- Número centralizado -->
<TextView
android:id="@+id/txt_resultado"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="20"
android:textColor="#FFFFFF"
android:textSize="80sp"
android:textStyle="bold"
android:shadowColor="#FF0000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="15" />
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_header"
android:layout_centerHorizontal="true"
android:layout_marginTop="300dp"
android:text="TOQUE PARA ROLAR"
android:textColor="#555555"
android:textSize="14sp" />
<!-- Botão de Ação -->
<Button
android:id="@+id/btn_rolar"
android:layout_width="match_parent"
android:layout_height="65dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="30dp"
android:backgroundTint="#D32F2F"
android:text="LANÇAR DADO"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
MainActivity.java)Disponível em: app/src/main/java/br/com/curso/dadosrpg/MainActivity.java
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();
}
}
—`
No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd android_p01_dado_3d
./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_p01_dado_3d