Mostrando postagens com marcador ViewHolder. Mostrar todas as postagens
Mostrando postagens com marcador ViewHolder. Mostrar todas as postagens

quinta-feira, 24 de março de 2011

Adapter Eficiente no Android

Olá povo,

Para preencher diversos componentes de UI como: ListView, Spinner (combo box) e GridView, o Android utiliza o conceito de Adapters. Esse é um padrão de projeto muito útil, e serve (como o próprio nome diz) para adaptar duas interfaces incompatíveis. No caso do Android, esse padrão serve para adaptar uma lista de objetos para uma lista elementos de interface gráfica, como as linhas de uma ListView.
Por exemplo, imagine uma lista de objetos da classe Pessoa, e você quer exibi-los na tela. O Android, através da classe ListView (que é um componente que exibe os componentes em forma de lista) solicita, para cada linha da lista um novo objeto View (que é a classe mãe de todos os elementos visuais do Android). Cabe ao Adapter, criar um objeto View que represente visualmente o objeto da posição específica da lista de objetos. Vamos detalhar mais como isso funciona na prática.
Crie um novo projeto no Eclipse marcando a opção para criar uma activity e nomeando-a como TelaListaActivity. Antes de mexer na Activity vamos criar a classe de objetos que queremos listar. No nosso caso, será a classe Pessoa que citamos acima.
public class Pessoa {
  private long id;
  private String nome;
  private String estado;

  public Pessoa(long id, String nome, String estado) {
     this.id = id;
     this.nome = nome;
     this.estado = estado;
  }

  public long getId() {
     return id;
  }
  public void setId(long id) {
     this.id = id;
  }
  public String getNome() {
     return nome;
  }
  public void setNome(String nome) {
     this.nome = nome;
  }
  public String getEstado() {
     return estado;
  }
  public void setEstado(String estado) {
     this.estado = estado;
  }
}

A classe acima não passa de um POJO (Plain Old Java Object), ou seja, apenas uma classe com métodos gets e sets. Agora vamos criar o arquivo de layout que representará cada linha a ser exibida na ListView. Crie o arquivo linha.xml dentro do diretório res/layout e deixe-o conforme abaixo:
<LinearLayout android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
   android:id="@+id/textView1"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:textsize="26dp"
   android:textcolor="#00FF00"/>
<TextView
   android:id="@+id/textView2"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
</LinearLayout>

Nosso layout também é bem simples. Consta apenas de um LinearLayout com dois TextViews, onde deixamos primeiro com o texto um pouco maior que o segundo e com a cor verde. Vamos agora criar nosso adaptador. Crie uma nova classe chamada PessoaAdapter e deixe-a conforme abaixo.
public class PessoaAdapter extends BaseAdapter {

   private Context ctx;
   private List<Pessoa> pessoas;

   public PessoaAdapter(Context ctx, 
      List <Pessoa> pessoas) {
      this.ctx = ctx;
      this.pessoas = pessoas;
   }

   @Override
   public int getCount() {
      return pessoas.size();
   }

   @Override
   public Object getItem(int position) {
      return pessoas.get(position);
   }

   @Override
   public long getItemId(int position) {
      return pessoas.get(position).getId();
   }

   @Override
   public View getView(int position, View convertView,
      ViewGroup parent) {
      
      Pessoa p = pessoas.get(position);

      View v = LayoutInflater.from(ctx).inflate(
         R.layout.linha, null);

      TextView txt1 = (TextView) v.findViewById(
         R.id.textView1);
      TextView txt2 = (TextView) v.findViewById(
         R.id.textView2);

      txt1.setText(p.getNome());
      txt2.setText(p.getEstado());

      return v;
   }
}

Nossa classe herda BaseAdapter e tem dois atributos que são inicializados no construtor da classe. O atributo da classe Context será utilizado para carregar o arquivo de layout que acabamos de criar. Já o segundo é a lista de objetos da classe Pessoas que queremos adaptar.
Quando criamos um adapter, devemos implementar quatro métodos: getCount, getItem, getItemId e getView. O primeiro retorna a quantidade de linhas que o adaptador representa, se estamos adaptando a lista de pessoas, basta retornar o tamanho da lista de pessoas.
O método getItem serve para acessarmos o objeto que o adaptador está representando. No nosso caso, basta retornar o objeto da posição da lista.
Já o método getItemId serve pra retornar um identificador do objeto da posição passada como parâmetro. Esse método é opcional, mas como temos um atributo chamado id na classe pessoa, ele passa a ser um bom candidato. Então obtemos o objeto da classe Pessoa da posição especificada pelo parâmetro e retornamos o seu id.
O último método é o mais importante. É ele que vai pegar um objeto da lista de pessoas, carregar o arquivo de layout e atribuir os valores dos atributos do objeto pessoa para a view que representará a linha da lista.
Para carregar o arquivo de layout, utilizamos a classe LayoutInflater. Uma vez que esse layout é carregado, é retornado um objeto View que representa a árvore de Views definida no arquivo XML, que no nosso caso, tem um LinearLayout e dentro dele, dois TextViews. Uma vez de posse desse objeto View, obtemos as referências para os TextViews para podemos passar os valores dos atributos da classe Pessoa para eles. No final retornamos a View que carregamos e modificamos.

Vamos agora para atividade da aplicação. Nela vamos criar uma lista de objetos pessoa, criar o uma instância do nosso adapter e defini-lo como "preenchedor" da lista.
public class TelaListaActivity extends ListActivity {

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      ArrayList<Pessoa> pessoas = 
         new ArrayList<Pessoa>();

      pessoas.add(
         new Pessoa(1, "Nelson", "Pernambuco"));
      pessoas.add(
         new Pessoa(2, "Glauber", "São Paulo"));
      pessoas.add(
         new Pessoa(3, "José", "Minas Gerais"));
      pessoas.add(
         new Pessoa(4, "João", "Rio de Janeiro"));
      pessoas.add(
         new Pessoa(5, "Silvio", "Pernambuco"));
      pessoas.add(
         new Pessoa(6, "Marta", "Pernambuco"));
 
      PessoaAdapter adapter = 
         new PessoaAdapter(this, pessoas);

      setListAdapter(adapter);
   }
}

Se executarmos nossa aplicação teremos um resultado similar à figura abaixo:

Muito bom! Nossa lista de objetos pessoa foi exibido perfeitamente na ListView que é criada automaticamente quando herdamos da classe ListActivity. Mas vamos recaptular um pouco... O método getView é chamado para cada linha da linha. Então se tivermos 10000 linhas na lista carregaremos 10000 layouts? Pois é, e isso não é bom. Mas não se desespere, os engenheiros da Google criaram uma forma padrão de otimizar essas listas. Altere o método getView para que fique conforme abaixo:
@Override
public View getView(int position, View convertView, 
   ViewGroup parent) {
  
   ViewHolder holder;

   Pessoa p = pessoas.get(position);

   if (convertView == null){
      convertView = LayoutInflater.from(ctx).inflate(
         R.layout.linha, null);
 
      holder = new ViewHolder();
      holder.txtNome = (TextView) 
         convertView.findViewById(R.id.textView1);
      holder.txtEstado = (TextView) 
         convertView.findViewById(R.id.textView2);
      convertView.setTag(holder);
   } else {
      holder = (ViewHolder)convertView.getTag();
   }
   holder.txtNome.setText(p.getNome());
   holder.txtEstado.setText(p.getEstado());

   return convertView;
}

static class ViewHolder {
   TextView txtNome;
   TextView txtEstado;
}

A classe ViewHolder vai manter as referências para a Views filhas para evitar chamadas desnecessárias ao método findViewById para cada linha. O parâmetro convertView representa uma linha pode ser reusada, uma vez que nem todas as linhas podem estar sendo vistas na tela. Quando convertView é diferente null, podemos reusá-la, então não será necessário carregar uma nova instância do layout. Se ela for nula, carregamos o layout e instanciamos um novo ViewHolder para armazenar as Views que iremos alterar, e em seguida setamos esse ViewHolder na propriedade tag (que pode armazenar qualquer objeto) da convertView.

O resultado para o número de registros que usamos não é aparente, mas tente adicionar 10000 objetos rodar a lista com o primeiro e o segundo exemplo :) Façam esse teste usando a instrução abaixo pra alimentar a lista;

for (int i = 0; i < 10000; i++) {
   pessoas.add(new Pessoa(i, "Pessoa "+ i, "Acre"));  
}


Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fonte: Android Developers e Google IO 2010