Páginas

quarta-feira, 26 de outubro de 2011

Android: Tratando mudança de orientação

Olá povo,

Chegaram 4 novos estagiários aqui no projeto e coube "a minha pessoa" orientá-los. Durante a aula, o assunto enveredou para tratamento de mudança de orientação do aparelho (portrait e landscape | retrato e paisagem).
Por padrão, ao girar o telefone o método onCreate é chamado novamente e com isso, dados que foram carregados dinamicamente (como itens de um ArrayList que estão preenchendo um ListView) são perdidos.

Vejamos o exemplo abaixo:

Arquivo de layout.
<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical"
 android:background="#0000FF">

 <TextView
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:text="Digite o nome" />

 <EditText
   android:id="@+id/editText1"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"/>

 <Button
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:onClick="meuBotaoClick"
   android:text="Adicionar" />

 <ListView
   android:id="@+id/listView1"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent" />
</LinearLayout>

Classe da Activity
public class TelaPrincipalActivity
 extends Activity {

 EditText edt;
 ArrayList<String> nomes;
 ArrayAdapter<String> adapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.main);

   edt = (EditText) findViewById(R.id.editText1);

   ListView listView = (ListView) 
     findViewById(R.id.listView1);
   adapter = new ArrayAdapter<String>(this,
     android.R.layout.simple_list_item_1, nomes);

   listView.setAdapter(adapter);
 }

 public void meuBotaoClick(View v) {
   nomes.add(edt.getText().toString());
   edt.setText("");
   adapter.notifyDataSetChanged();
 }
}

No código acima, ao clicar no botão, o texto digitado no EditText é inserido na lista, e em seguida o adapter é atualizado através do método notifyDataSetChanged.
O exemplo é bem simples, mas se você girarmos o aparelho os dados que estão sendo exibidos são perdidos, uma vez que a lista inicia vazia e (como já falei) o onCreate é chamado novamente.

Para isso não acontecer você tem 3 alternativas:

1)Forçar uma orientação
Na declaração da sua Activity, no AndroidManifest.xml, você pode usar a propriedade android:screenOrientation para manter sua aplicação em uma orientação específica (portrait/landscape).
<activity
 android:name="TelaPrincipalActivity"
 android:screenOrientation="portrait" />


2)Avisar o Android para não chamar o onCreate
Essa é a melhor opção quando você quer utilizar a tela nas duas orientações e impedir que o Android chame o onCreate cada vez que você girar o aparelho. Para isso basta dizer que ao mudar a configuração (configChanges) de orientação, ele não recrie a Activity.
<activity
 android:name="TelaPrincipalActivity"
 android:configChanges="orientation|keyboardHidden|screenSize"/>


3) Salvar o estado da Activity
A opção número dois é perfeita quando você quer usar o mesmo layout nas duas orientações. Mas, e se eu quiser usar layouts diferentes? Neste caso você dever permitir que a Activity seja recriada. Entretanto, devemos salvar seu estado com o método onSaveInstanceState e no onCreate recuperar esse estado.

No exemplo que fiz os "estags", criei a pasta "layout-land" com o mesmo layout definido acima, só mudando o background (apenas para notarmos os diferentes layouts). Depois implementamos o método onSaveInstanceState para salvar os itens da lista.
@Override
protected void onSaveInstanceState(Bundle outState) {
 super.onSaveInstanceState(outState);
 outState.putStringArrayList("nomes", nomes);
}

Se vocês notaram, o método onSaveInstanceState usa um objeto da classe Bundle para salvar o estado. Esse estado é passado para o método onCreate da Activity como parâmetro, que utilizaremos para restaurar o seu estado.
@Override
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 // Código já apresentado
 if (savedInstanceState != null) {
   nomes = savedInstanceState.
    getStringArrayList("nomes");

 } else {
   nomes = new ArrayList<String>();
 }
}


Com isso podemos ter, para a mesma tela, um layout diferente para cada orientação, desde que tenhamos os mesmos componentes (e com os mesmos ids).

Ficou com dúvida? Deixe seu comentário.

4br4ç05,
nglauber

21 comentários:

  1. Ótimas dicas sobre orientação. Essa postagem me ajudou a resolver uma CR =D

    ResponderExcluir
  2. Achei bastante interessante a abordagem, muito útil. Mas estou com uma certa dúvida na hora de armazenar os valores de uma matriz, qual seria a função para isso?

    ResponderExcluir
  3. Perfeito!!

    Estava procurando exatamente o que você escreveu!!

    Parabéns.

    ResponderExcluir
  4. muito obrigado por ter me ajudado com a dica da orientacao, caso tenha algum tutorial sobre bluetooth ta agradeceria muito.

    ResponderExcluir
  5. Oi Anônimo,

    Escrevi um artigo para a revista Mobile Magazine (http://nglauber.blogspot.com/2011/04/artigo-comunicacao-via-bluetooth-no.html).
    Mas você pode achar um exemplo no próprio diretório do SDK.

    4br4ç05,
    nglauber

    ResponderExcluir
  6. Excelente Glauber, foi muito útil para mim.

    ResponderExcluir
  7. Excelente tutorial, parabéns! Era exatamente o que eu estava procurando... me ajudou muito

    ResponderExcluir
  8. Excelente tutorial, sem dúvidas vai me ajudou muito!
    Só tenho uma dúvida:
    No exemplo, você usou um único layout, e criou a pasta para o layout em paisagem, o "/layout-land". Já no meu caso estou trabalhando com quatro pastas: "layout", "layout-large", "layout-small" e "layout-xlarge". No meu caso, como devo nomear as pastas para layout pasagem? Seria "/layout-small-land", por exemplo?
    Obrigado desde já. Seu blog é otimo!

    ResponderExcluir
  9. Oi Rodrigo,

    Você pode encontrar a resposta aqui:
    http://developer.android.com/guide/topics/resources/providing-resources.html

    Segundo a documentação, o tamanho vem antes da orientação. Então fica layout-small-land, por exemplo.

    4br4ç05,
    nglauber

    ResponderExcluir
  10. Perfeito!!!
    Faz um tempão que eu estava procurando a solução numero 2!!!
    Parabens e obrigado!!!

    ResponderExcluir
  11. Oi Nelson, gostaria de saber se é possível colocar em um mobile site html uma função script que não deixe o tablet/smartphone girar, deixar ele sempre em portrait?

    Obrigado.

    ResponderExcluir
  12. Oi Maurício,

    Você não tem um controle tão grande sobre o browser do Android (pelo menos não que eu saiba). Mas você pode saber quando a orientação mudou e tomar alguma ação. O jQuery Mobile, por exemplo, dá esse suporte.

    4br4ç05,
    nglauber

    ResponderExcluir
  13. Bom eu gostei muito dessa postagem, eu usei na minha aplicação e funcionou quanto eu utilizei smartphone,





    mais quando eu utilizei tablets não funcionou, tem como me ajudar para funcionar em tablets.

    ResponderExcluir
  14. Oi Enzo,

    Qual foi o problema?

    4br4ç05,
    nglauber

    ResponderExcluir
  15. cara ajudou muito!!! usei quase tudo na minha aplicação..
    obrigado e parabens!

    ResponderExcluir
  16. Oi joão paulo,
    Qual é o problema??

    4br4ç05,
    nglauber

    ResponderExcluir
  17. Estou criando um APP que mostra a galeria de vídeo da minha página do Wordpress. O APP roda direitinho mas quando mudo a orientação de Retrato para Paisagem o vídeo recarrega novamente. Li seu tutorial só que no exemplo você fez a recuperação com String. Como ficaria a recuperação para os vídeos continuarem de onde estavam, quando giramos a tela?Utilizo WebView. .

    ResponderExcluir
  18. Oi Sérgio,

    O seu caso é uma exceção mesmo. Como é uma Webview e é um vídeo, é melhor utilizar a solução número 2 e seguir essas recomendações aqui.
    https://developer.android.com/guide/topics/resources/runtime-changes.html

    4br4ç05,
    nglauber

    ResponderExcluir