Personalizando uma ListView no Android

Personalizando uma ListView no Android
Alex Felipe
Alex Felipe

Compartilhe

Vou personalizar uma ListView no Android para que cada item da lista tenha seu próprio visual, dando uma maior flexibilidade para o desenvolvimento minha app.

No artigo sobre como criar listas com o ListView que eu escrevi anteriormente, vimos como é possível criar uma lista bem básica no Android. O resultado da lista criada foi:

tela-lista-com-cursos

Mas pensando bem, não era exatamente uma lista assim que eu queria... Em outras palavras, seria melhor se cada item tivesse um layout e um design da minha preferência como, por exemplo, esse item que eu criei:

item-personzalido-lista

Código fonte:


<?xml version="1.0" encoding="utf-8"?> 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="100dp" android:orientation="horizontal">

<ImageView android:id="@+id/lista_curso_personalizada_imagem" android:layout_width="100dp" android:layout_height="match_parent" />

<LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical">

<TextView android:id="@+id/lista_curso_personalizada_nome" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Titulo" android:textSize="30dp" />

<TextView android:id="@+id/lista_curso_personalizada_descricao" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="descriçao" android:textSize="20dp" />

</LinearLayout>

</LinearLayout>

Mas como será que podemos fazer isso?

Bem... Lembra como fazíamos para adicionar um item na lista? Nós utilizamos a classe ArrayAdapter do Android que era responsável em adaptar itens em uma ListView!

Mas ela é uma implementação já pronta do Android e não conseguimos manipular esse adapter da forma que desejamos...

E agora? Bom, se não podemos modificar a implementação do Android, precisamos implementar a nossa! E como podemos implementar um adapter nosso?

Banner da Escola de Mobile: Matricula-se na escola de Mobile. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!

Criando nosso próprio Adapter

Para a nossa felicidade, o Android nos fornece a classe BaseAdapter que permite a criação de um adapter personalizado! Então vamos criar uma nova classe que representará o nosso novo adapter e vamos estender a classe BaseAdapter:


public class AdapterCursosPersonalizado extends BaseAdapter {

}

Implementando métodos necessários do BaseAdapter

Porém, a classe BaseAdapter possui 4 métodos abstratos, ou seja, métodos que a implementação é obrigatória! Precisamos implementar esses métodos:


public class AdapterCursosPersonalizado extends BaseAdapter {

@Override public int getCount() { return 0; }

@Override public Object getItem(int position) { return null; }

@Override public long getItemId(int position) { return 0; }

@Override public View getView(int position, View convertView, ViewGroup parent) {
   return null; } 

}

Observe os 3 primeiros métodos: getCount(), getItem(int position), getItemId(int position). Perceba que são métodos relacionados a uma lista! Para implementá-los da maneira correta, precisamos de uma lista dentro do nosso adapter.

Lembra que enviávamos a nossa lista via construtor no ArrayAdapter? Faremos o mesmo no nosso adapter para que possamos implementar esses métodos da maneira esperada:


public class AdapterCursosPersonalizado extends BaseAdapter {

private final List<Curso> cursos;

public AdapterCursosPersonalizado(List<Curso> cursos, Activity act) {
   this.cursos = cursos; }

//métodos

}

Informando o total de itens da lista

Agora podemos implementar os nossos métodos! Vamos começar pelo getCount(). O próprio método já diz o que ele faz: conta quantos itens existem na lista. Ou seja, o tamanho da lista.


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

Devolvendo o item da lista pela posição

Agora vamos para o getItem(int position). Veja que ele quer saber um item a partir de uma posição. Isso é fácil! Basta apenas retornamos por meio do método get() mandando a posição:


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

Devolvendo o id do item da lista

Vejamos o próximo: getItemId(int position). Esse método espera saber qual é o id do objeto que está sendo buscado. Porém, se verificarmos a nossa classe que representa um curso:


public class Curso {

private String nome;
private String descricao; 
private EstadoAtual estado;

//métodos

}

Veja que ela não possui um id. Para esse caso nós temos duas alternativas:

  • 1) Manter o retorno como 0;
  • 2) Adicionar o id ao curso e, pegar o objeto pelo método get() e então usar o getter do id.

Atualmente, não precisamos do id, então, por enquanto, devolveremos 0.

Criando a View para cada item

Ótimo, implementamos os 3 primeiro métodos referente a lista que enviamos, porém ainda falta mais 1 que é o getView():


@Override public View getView(int position, View convertView, ViewGroup parent) {
   return null; }

Repare que agora ele retorna uma View, ou seja, esse é o método responsável pela construção de cada item! O que precisamos para implementá-lo?

Inicialmente, precisamos, de alguma forma, pegar a View que representa o nosso layout personalizado. Afinal é ela que queremos apresentar na nossa lista!

Mas, para pegar uma View, nós precisamos de uma Activity e a nossa classe, além de não ser uma Activity, não possui uma Activity.

E agora? O que faremos? Se dermos uma olhada na forma que fizemos para instanciar o ArrayAdapter anteriormente:


ArrayAdapter<Curso> adapter = new ArrayAdapter<Curso>(this, android.R.layout.simple_list_item_1, cursos);

Veja que estamos passando passando o parâmetro this que representa o objeto da própria Activity que está fazendo a chamada. Precisamos receber também essa Activity via construtor:


public class AdapterCursosPersonalizado extends BaseAdapter {

private final List<Curso> cursos; 
private final Activity act;

public AdapterCursosPersonalizado(List<Curso> cursos, Activity act) { 
  this.cursos = cursos; 
  this.act = act; 
}

//métodos

}

Agora sim podemos chamar uma View!

Queremos criar uma View, ou seja, ao invés de só buscá-la via o método findViewById(), nós iremos criar a View.

Em outras palavras, inflar uma View! E para isso iremos utilizar o método getLayoutInflater() da Activity que é responsável em inflar uma View:


@Override public View getView(int position, View convertView, ViewGroup parent) { 
act.getLayoutInflater() 
return null; }

Pegamos o responsável em inflar e chamaremos o método inflate() que criará a View e a retornará para nós:


@Override public View getView(int position, View convertView, ViewGroup parent) { 
View view = act.getLayoutInflater().inflate(R.layout.lista_curso_personalizada, parent, false); 
return null; }

Perceba que utilizamos o parent que vem como parâmetro do método getView(). Mas o que ele representa? Como podemos ver, o parent é a própria ViewGroup, ou seja, o layout pai ao qual iremos adicionar a nossa lista, por isso enviamos ele.

Além disso, ainda existe o último parâmetro que recebe um valor booleano, esse parâmetro indica se queremos criar, nesse exato momento a View.

Mas, não fizemos nenhum tipo de alteração como adicionar as informações do curso, por isso mandamos o false. Dessa forma, podemos associar tudo que queremos e só depois ele criará de fato a View :)

Certo, pegamos a nossa View e agora precisamos de um curso, certo? Mas qual curso?

Veja que, ainda existe um parâmetro no getView() que é o position, ou seja, é justamente nessa posição que devemos pegar o elemento da lista que foi passada, nesse nosso caso, o curso:


@Override public View getView(int position, View convertView, ViewGroup parent) { 
View view = act.getLayoutInflater().inflate(R.layout.lista_curso_personalizada, parent, false); 
Curso curso = cursos.get(position);
return null; 
}

Preenchendo os valores para cada item da lista

Ótimo! Agora já podemos chamar as outras Views e preencher as informações e então, retornar o objeto view:


@Override public View getView(int position, View convertView, ViewGroup parent) { 
View view = act.getLayoutInflater().inflate(R.layout.lista_curso_personalizada, parent, false); 
Curso curso = cursos.get(position);

//pegando as referências das Views 
TextView nome = (TextView) view.findViewById(R.id.lista_curso_personalizada_nome); 
TextView descricao = (TextView) view.findViewById(R.id.lista_curso_personalizada_descricao); 
ImageView imagem = (ImageView) view.findViewById(R.id.lista_curso_personalizada_imagem);

//populando as Views 
nome.setText(curso.getNome()); 
descricao.setText(curso.getDescricao()); 
imagem.setImageResource(R.drawable.java);

return view; 
}

O nosso próprio adapter está implementado:


public class AdapterCursosPersonalizado extends BaseAdapter {

private final List<Curso> cursos; private final Activity act;

public AdapterCursosPersonalizado(List<Curso> cursos, Activity act) { this.cursos = cursos; this.act = act; }

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

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

@Override public long getItemId(int position) { return 0; }

@Override public View getView(int position, View convertView, ViewGroup parent) {

View view = act.getLayoutInflater().inflate(R.layout.lista_curso_personalizada, parent, false);

Curso curso = cursos.get(position);

TextView nome = (TextView) view.findViewById(R.id.lista_curso_personalizada_nome); 
TextView descricao = (TextView) view.findViewById(R.id.lista_curso_personalizada_descricao); 
ImageView imagem = (ImageView) view.findViewById(R.id.lista_curso_personalizada_imagem);

nome.setText(curso.getNome()); 
descricao.setText(curso.getDescricao()); 
imagem.setImageResource(R.drawable.java);

return view; } }

Para testarmos a nossa implementação, basta apenas alterar na Activity, que chamava o ArrayAdapter do Android, para chamar o nosso adapter:


public class ListaDeCursosActivity extends AppCompatActivity {

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

List<Curso> cursos = todosOsCursos();

ListView listaDeCursos = (ListView) findViewById(R.id.lista);

//chamada da implementaçao do android: ArrayAdapter<Curso> 

adapter = new ArrayAdapter<Curso>(this, //android.R.layout.simple_list_item_1, cursos); 

//chamada da nossa implementação AdapterCursosPersonalizado 

adapter = new AdapterCursosPersonalizado(cursos, this);

listaDeCursos.setAdapter(adapter);

}

//métodos

}

Observe que não foi apresentada a implementação do método todosOsCursos() justamente para ser mais objetivo na implementação do componente ListView. Compreenda esse método como um acesso aos dados qualquer, seja uma lista estática, banco de dados ou qualquer meio que devolva uma lista de cursos.

Agora se testarmos a nossa app:

tela-lista-personalizada1

Adicionando uma imagem diferente para cada item da lista

A nossa lista mudou, porém ainda há uma coisa bem bizarra: no curso de Java temos a imagem do Java, no de HTML também, e no de Android também! E não era isso que nós queríamos!

Nós queremos que, para cada curso, seja usada uma imagem que identifique-o. Por exemplo: curso de Java imagem de Java, curso de HTML, imagem de HTML e assim por diante. O que será que erramos?

Vejamos como está sendo inserida a imagem no getView() do nosso adapter:

@Override public View getView(int position, View convertView, ViewGroup parent) {

//código

Curso curso = cursos.get(position);

ImageView imagem = (ImageView) view.findViewById(R.id.lista_curso_personalizada_imagem);

imagem.setImageResource(R.drawable.java);

return view; 
}

Observe que estamos setando a mesma imagem para todos os elementos da lista! Precisamos de alguma informação do curso para sabermos a que ele se refere!

Atualmente, não temos nenhum tipo de informação para categorizar os nossos cursos, então que tal criarmos um enum para isso?


public enum Categoria {

JAVA, HTML, ANDROID 
}

E agora adicionamos um enum para a nossa classe curso:


public class Curso {

private long id; 
private String nome; 
private String descricao; 
private EstadoAtual estado; 
private Categoria categoria;

//métodos

}

Muito bom! Agora basta verificarmos a qual categoria o curso refere-se e então settamos a imagem apropriada:

@Override public View getView(int position, View convertView, ViewGroup parent) {

//código

Curso curso = cursos.get(position);

ImageView imagem = (ImageView) view.findViewById(R.id.lista_curso_personalizada_imagem);

Categoria categoria = curso.getCategoria();

if (categoria.equals(Categoria.JAVA)) { imagem.setImageResource(R.drawable.java); } 
else if (categoria.equals(Categoria.ANDROID)) {
   imagem.setImageResource(R.drawable.android); }
    else if (categoria.equals(Categoria.HTML)) { 
      imagem.setImageResource(R.drawable.html); 
      }

return view; }

Essa solução com esse tanto de if e else funciona, porém não é uma boa prática! Nesse post eu detalho um dos grandes problemas que temos com esse tipo de solução e como podemos resolver de uma maneira mais elegante.

Adicionamos as nossas condições para setar as imagens, então agora vamos testar e ver o resultado:

tela-lista-personalizada2

Excelente! A nossa lista personalizada foi criada conforme o esperado!

Aprendendo um pouco mais

Embora tenhamos aprendido a implementar e personalizar uma ListView ainda existe mais um detalhe importante durante a implementação de um Adapter que é justamente o padrão ViewHolder.

Não será explicado sobre esse padrão, entretanto, o Matheus escreveu um excelente post explicando a teoria e como é possível implementar o ViewHolder na ListView.

Resumo

Vimos que, para criar uma lista personalizada, nós precisamos fazer uma implementação nossa estendo da classe BaseAdapter que permite a criação de um adapter personalizado.

Vimos também que temos que especificar tudo que iremos adicionar na lista, como por exemplo, as informações do curso para sua View específica.

Além disso, vimos que podemos declarar condições no método getView() para adicionar conteúdo diferente de acordo com algum critério, como foi o caso da imagem específica para cada curso.

Código fonte

Caso tiver dúvidas ou simplesmente quiser consultar o código fonte do projeto utilizado como exemplo, fique à vontade de dar uma olhada no github. Lembrando que para essa etapa do projeto eu fiz uso da branch lista-personalizada.

E aí, gostou de criar uma lista própria? Quer aprender mais dicas sobre o Android? Que tal conhecer a formação Android.

Alex Felipe
Alex Felipe

Alex é instrutor e desenvolvedor e possui experiência em Java, Kotlin, Android. Atualmente cria conteúdo no canal https://www.youtube.com/@AlexFelipeDev.

Veja outros artigos sobre Mobile