sexta-feira, 26 de agosto de 2011

Fazendo uma tela de splash

Olá povo,

É muito comum vermos nas aplicações a famosa "tela de splash", ou tela de loading. É nela que a aplicação dá um feedback pro usuário informando que os recursos necessários para execução da aplicação estão sendo carregados. Após verificar uma dessas telas elaborada por uma equipe do projeto em que trabalho, resolvi dar minha sugestão de como implementá-la.

[EDITADO 08/02/2016]
O código a seguir é basicamente o que está sendo descrito nesse post do Ian Lake.
https://plus.google.com/+AndroidDevelopers/posts/Z1Wwainpjhd

Basicamente você precisa definir um tema para sua Activity.
<style name="AppTheme.Launcher">
  <item name="android:windowBackground">@drawable/launch_screen</item>
</style>
Em seguida defina esse tema na sua activity MAIN/LAUNCHER (no AndroidManifest.xml). Perceba que "launch_screen" não precisa ser necessariamente uma imagem PNG, pode ser um XML como a seguir.
<layer-list 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:opacity="opaque">
  <!-- Uma cor de background, preferencialmente a mesma do tema -->
  <item android:drawable="@android:color/white"/>
  <!-- Logo do app. Imagem de 144dp -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>
Depois é só mudar o tema em tempo de execução para o tema que você preferir.
public class MyMainActivity extends AppCompatActivity {
 @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Essa linha tem que ser chamada antes do super.onCreate
    setTheme(R.style.Theme_MyApp);
    super.onCreate(savedInstanceState);
    // ...
  }
}

[/EDITADO]
[EDITADO 20/08/2016]
Ótima apresentação do Cyril Mottier sobre splash screens.
http://cyrilmottier.com/2016/06/20/launch-screens-from-a-tap-to-your-app/
[/EDITADO]

Essa implementação não é recomendada (ao contrário da mostrada acima), mas já precisei fazer por exigência de cliente.
Vamos começar pela classe que realiza esse trabalho.
public class SplashActivity extends Activity
 implements Runnable {
 private Handler handler;

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

   handler = new Handler();
   handler.postDelayed(this, 2000);
 }

 @Override
 protected void onPause() {
   super.onPause();

   handler.removeCallbacks(this);
 }

 @Override
 protected void onRestart() {
   super.onRestart();

   run(); 
 }

 @Override
 public void run() {
   // Faça o carregamento necessário aqui...
   
   // Depois abre a atividade principal e fecha esta
   Intent it = new Intent(
     this, PrincipalActivity.class);
   startActivity(it);

   finish();

   overridePendingTransition(
     android.R.anim.fade_in, android.R.anim.fade_out);
 }
}

A Activity está implementando Runnable para carregaremos os recursos da aplicação em uma Thread separada, uma vez que não queremos dar a impressão de que ela está travada. Em seguida, temos um atributo do tipo Handler, ele é utilizado para atualizar os componentes da tela (informando o progresso por exemplo) através de outra Thread, uma vez que é uma restrição do Android permitir que apenas a Thread da UI (ou seja, a Thread principal) faça isso.
Ao criar a atividade (onCreate), inicializamos o Handler e agendamos a execução da Thread de carregamento dos recursos para ser executada daqui a 2 segundos (2000 milissegundos). Como não estou carregando nada nesse exemplo, estou usando esse valor pra dar tempo de ver a splash. Na prática, esse valor deve ir para um valor mais baixo (0,5 segundo por exemplo). No método onPause, eu estou tratando o caso de o usuário clicar na tecla "Back" durante o carregamento da splash. Caso isso aconteça, o carregamento é cancelado.
No método run, que será executado em background, devemos fazer o carregamento dos recursos da aplicação. Em seguida, criamos a Intent para a tela principal da aplicação e a chamamos com o startActivity. Por fim, finalizamos a tela de splash com o método finish.
Um recurso interessante que coloquei nesse exemplo foi a mudança da transição padrão entre as telas. Isso é feito através do método overridePendingTransition, que recebe uma animação para a tela que será exibida e outra para a tela q está sendo fechada. Nesse exemplo, usei um efeito de transparência (Fade In e Fade Out) da classe R do próprio Android. Se quiser fazer a sua própria, eu coloquei um post sobre isso aqui no blog.
De código é só. O que não podemos esquecer, é de declarar a Activity no AndroidManifest.xml, e as telas de Splash e Principal têm uma configuração interessante.
<activity
 android:name=".SplashActivity">
 <intent-filter>
   <action 
      android:name="android.intent.action.MAIN" />
   <category 
      android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
</activity>

<activity
 android:name=".PrincipalActivity">
 <intent-filter>
   <action 
      android:name="android.intent.action.MAIN" />
   <category 
      android:name="android.intent.category.DEFAULT" />
 </intent-filter>
</activity>
Notem que as duas atividades têm a ação android.intent.action.MAIN que informa ao Android que elas são pontos de partida da aplicação. Entretanto, a tela de Splash tem a categoria android.intent.category.LAUNCHER, enquanto que a principal tem a categoria android.intent.category.DEFAULT. O que isso significa?
Ao clicar no ícone no menu principal de aplicações do Android (Launcher) a SplashActivity é executada (isso graças a categoria LAUNCHER), e como vimos, em seguida ela chama a PrincipalActivity e se finaliza (com o finish).
Quando clicamos na tecla "Back" na tela Principal, a aplicação "morreu", e se a executarmos novamente a splash será aberta novamente. Porém, se saírmos da aplicação com a tecla "Home", a PrincipalActivity deve ficar lá suspensa, para que se o usuário clique no ícone da aplicação novamente, a tela principal (que foi aberta anteriormente) seja exibida. Isso funciona graças a categoria DEFAULT que foi passada para a PrincipalActivity.
Por hoje é só pessoal. Qualque dúvida, deixem seus comentários.
[EDITADO 15/03/2016]
Recentemente tive que fazer a tela de splash da maneira não recomendada, mas ela precisava ocultar também a barra de status e os botões de navegação. Sendo assim, precisei colocar o código a seguir no onCreate.
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);

setContentView(R.layout.splash_layout);
[/EDITADO]
4br4ç05,
nglauber
P.S.: O exemplo acima pode ser feito com AsyncTask também. Fica o desafio pra quem quiser ;)

2 comentários:

Adriano Avelar disse...

Show de bola. Sempre que preciso de uma splash screen venho para esse post :) Vlw Glauber.

Alberto Lourenço disse...

Que post lindo! Uma benção.. ehhe! :D