Java com café: março 2013

ProgressBar dentro de uma ActionBar - Android

Olá! Hoje vou mostrar como adicionar uma progressbar dentro de uma actionbar. Um bom exemplo de implementação deste recurso é o aplicativo do Gmail, onde você tem um item de menu (usado para atualizar seus e-mails) que quando clicado, é substituído pela progressbar até o procedimento ser concluído e então volta novamente a ser o item.
Para utilizar este recurso, a versão mínima requerida do sdk é a 11. Você também vai precisar dos ícones, que podem variar de acordo com o tema (baixe o pacote neste link). Para o exemplo estarei utilizando estes que são adequados para o tema Holo Light:
res/drawable-mdpi
res/drawable-hdpi
res/drawable-xhdpi

Para começar salve os ícones nas respectivas pastas de acordo com as legendas nas imagens. Agora vamos precisar de dois arquivos xml, um para o item de menu e outro para a progressbar. Para o item, dentro da pasta res/menu, crie um arquivo chamado atualizar.xml com o seguinte conteúdo:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:id="@+id/action_atualizar"
        android:icon="@drawable/ic_menu_refresh"
        android:showAsAction="always"
        android:title="Atualizar"/>
</menu>
E na pasta res/layout o arquivo actionbar_atualizar.xml:
<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="30dp"
    android:layout_gravity="right"
    android:paddingRight="5dp" />
Para controlar quando vai aparecer o item ou a progressbar, a estrutura da classe deverá ser parecida com a que segue (odeio colar trechos muito grandes de código no site, mas paciência que vou explicar tudo lá embaixo):
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity {

 private boolean atualizando = false;
 private Menu mOptionsMenu;

 @Override
 protected void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getActionBar().setDisplayHomeAsUpEnabled(true);

 }

 @Override
 public void onBackPressed() {

  if (atualizando == true) {
   Toast.makeText(this, "Aguarde...", Toast.LENGTH_LONG).show();
  } else {
   super.onBackPressed();
  }

 }

 private void atualizar() {

  atualizando = true;
  setAtualizando(atualizando);

  new Operacao().execute();

 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {

  switch (item.getItemId()) {

  case android.R.id.home:
   onBackPressed();
   break;

  case R.id.action_atualizar:
   atualizar();
   break;

  }

  return true;

 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {

  super.onCreateOptionsMenu(menu);
  mOptionsMenu = menu;
  getMenuInflater().inflate(R.menu.atualizar, menu);
  // atualizar();
  return true;

 }

 public void setAtualizando(boolean atualizando) {

  if (mOptionsMenu == null) {
   return;
  }

  MenuItem atualizarItem = mOptionsMenu.findItem(R.id.action_atualizar);

  if (atualizarItem != null) {

   if (atualizando) {
    atualizarItem.setActionView(R.layout.actionbar_atualizar);
   } else {
    atualizarItem.setActionView(null);
   }

  }

 }

 class Operacao extends AsyncTask<Void, Void, Void> {

  Exception exception = null;

  @Override
  protected Void doInBackground(Void... params) {

   try {

    // Neste bloco vocẽ executa suas operações
    Thread.sleep(2000);

   } catch (Exception e) {
    exception = e;
   }

   return null;

  }

  @Override
  protected void onPostExecute(Void result) {
   super.onPostExecute(result);

   if (exception == null) {
    atualizando = false;
    setAtualizando(atualizando);
   } else {
    Toast.makeText(MainActivity.this,
      "Ocorreu um erro: " + exception.getMessage(),
      Toast.LENGTH_LONG).show();
   }

  }

 }

}
Bom, no começo temos duas variáveis: atualizando e mOptionsMenu. A primeira é simplesmente para identificar se a atualização está ocorrendo ou não em determinado momento. A segunda receberá o menu assim que ele for criado e permitirá a troca entre o item e a progressbar quando necessário.
Em seguida temos os seguintes métodos:
  • onCreate: Não tem nada de especial além do comando getActionBar().setDisplayHomeAsUpEnabled(true), que adiciona o item "voltar" na actionbar
  • onBackPressed: É tratado se a atualização está ocorrendo e não permitirá voltar se for verdade.
  • atualizar: Configura a barra com o setAtualizando e executa a tarefa que executará as operações de atualização. Falo sobre a classe que executa a tarefa logo abaixo.
  • onOptionsItemSelected: Verifica qual item do menu foi clicado e executa a ação correspondente.
  • onCreateOptionsMenu: A variável recebe o menu e caso queira que a Activity já inicie atualizando, basta descomentar o comando atualizar().
  • setAtualizando: É o responsável por mostrar o item ou a progressbar na actionbar
Por fim temos a classe interna Operacao, uma subclasse de AsyncTask que nos permite executar tarefas em background e atualizar a interface sem precisar de threads. Não vou entrar em detalhes sobre este tipo de classe, apenas saiba que o método doInBackground é usado para executar as operações (no exemplo chamei Thread.sleep(2000) apenas pra simular um procedimento rápido) e onPostExecute é executado quando doInBackground finalizar. E temos a variável exception que é usada pelos métodos para descobrir se a operação finalizou com sucesso.

Qualquer dúvida deixe nos comentários!