O Google não recomenda mais o uso de ListViews personalizados para novos apps, no lugar desse componente, estão recomendando o uso de RecyclerView, tal qual eu mostro nessa série de dois posts. Continue com esse tutorial apenas se realmente precisar usar ListView (projetos antigos, por exemplo).
O widget (ou container) ListView é muito popular em apps Android como sendo a forma padrão de listagem de resultados nesta plataforma. Entretanto, 10 entre 10 desenvolvedores Android detesta a aparência padrão deste componente que de forma puramente textual tenta oferecer ao usuário uma boa experiência. Ok, para listar configurações do smartphone pode até funcionar, mas e quando queremos combinar diversas informações em cada item? Quando queremos colocar imagens? Quando queremos não ter apenas uma String mas um objeto da nossa aplicação em cada item da ListView? Este post trata exatamente sobre isso, como criar ListViews com itens personalizáveis.
A IDE que utilizei para o desenvolvimento foi a Android Studio, que pode ser baixada gratuitamente em http://developer.android.com. Mais informações de setup e como criar seu primeiro projeto, dê uma olhada neste tutorial!
Começando!
Comece com o layout abaixo, onde temos um campo de busca com botão no topo e uma ListView logo abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" android:paddingbottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <EditText android:layout_width="fill_parent" android:layout_height="45dp" android:maxlength="10" android:id="@+id/txtPesquisa" android:layout_alignparenttop="true" android:layout_alignparentleft="true"></EditText> <Button android:layout_width="80dp" android:layout_height="45dp" android:text="Buscar" android:onclick="btnBuscar_OnClick" android:id="@+id/btnBuscar" android:layout_alignparenttop="true" android:layout_alignparentright="true"></Button> <ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/lstResultados" android:layout_below="@+id/txtPesquisa" android:layout_margintop="10dp"></ListView> </RelativeLayout> |
Visualmente este XML se transforma no seguinte layout:
Para definir um layout personalizado para os itens de uma ListView, independente se é uma listview de tela cheia ou compondo uma interface com outros componentes, você precisará criar um novo layout XML apenas para um item de modelo. O processo é simples: clique direito do mouse sobre a pasta de layouts e mande criar um novo layout XML file com o nome de item_modelo e com o gerenciador RelativeLayout. Nosso layout exibirá o nome da cidade, a sigla do estado, uma descrição da cidade e uma imagem pequena (thumb), como na imagem abaixo.
Para chegar no layout acima, precisaremos adicionar em nosso item_modelo.xml dois componentes TextView (large), um TextView (medium) e um ImageView, definindo as propriedades de orientação para que eles fiquem dispostos da maneira acima. Note que as imagens e informações que serão utilizadas foram pegos da Internet e podem ser substituídas pelo que você quiser (as imagens vão na psta drawable). O seu XML deve se parecer com o abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imgCidade" android:src="@drawable/florianopolis" android:layout_marginright="10dp" android:layout_alignparenttop="true" android:layout_alignparentleft="true"> </ImageView> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textappearance="?android:attr/textAppearanceLarge" android:text="Florianópolis" android:id="@+id/lblCidade" android:layout_alignparenttop="true" android:layout_torightof="@+id/imgCidade"> </TextView> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textappearance="?android:attr/textAppearanceLarge" android:text="/SC" android:id="@+id/lblUF" android:layout_alignparenttop="true" android:layout_marginleft="10dp" android:layout_torightof="@+id/lblCidade"> </TextView> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textappearance="?android:attr/textAppearanceMedium" android:text="Capital de Santa Catarina" android:id="@+id/lblDescricao" android:textcolor="@android:color/darker_gray" android:layout_below="@+id/lblCidade" android:layout_torightof="@+id/imgCidade"> </TextView> </RelativeLayout> |
Agora que temos o layout personalizado que será utilizado para renderizar os itens da nossa ListView, é hora de criarmos a classe cujos objetos irão guardar as informações a serem exibidas. Nossa classe Cidade deverá se parecer com a abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.example.fernando.exemplolistview; public class Cidade { private String nome; private String descricao; private String uf; private int resIdImagem; public Cidade(String nome, String descricao, String uf, int resIdImagem){ this.nome = nome; this.descricao = descricao; this.uf = uf; this.resIdImagem = resIdImagem; } public String getNome(){ return this.nome; } public String getDescricao(){ return this.descricao; } public String getUf(){ return this.uf; } public int getIdImagem(){ return this.resIdImagem; } } |
Note o uso de um inteiro para armazenar a informação da imagem, o que nos facilitará o trabalho mais para frente.
Muitas vezes não podemos ou não queremos criar uma nova classe para exibir itens personalizados na lista. Nestas ocasiões você pode substituir a criação de uma classe pelo uso de uma coleção Map, contendo chaves e valores.
Considerando o carregamento dos widgets em variáveis locais usando findViewById e a coleção de cidades abaixo, que deve ser criada no topo da sua Activity (e que poderia ser substituída por um SELECT no banco de dados ou um GET em uma API remota, teremos todas nossas cidades carregadas em memória. Note como defini o código da imagem usando a classe R e sua propriedade estática drawable, que lista os ids únicos das imagens. Isso evita ter de conhecer o caminho até o arquivo físico da imagem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); cidades = new ArrayList<>(); cidades.add(new Cidade("Florianópolis", "Capital de Santa Catarina", "SC", R.drawable.florianopolis)); cidades.add(new Cidade("Curitiba", "Capital do Paraná", "PR", R.drawable.curitiba)); cidades.add(new Cidade("São Paulo", "Capital de São Paulo", "SP",R.drawable.sao_paulo)); cidades.add(new Cidade("Porto Alegre", "Capital do Rio Grande do Sul", "RS", R.drawable.porto_alegre)); txtPesquisa = (EditText)findViewById(R.id.txtPesquisa); btnBuscar = (Button)findViewById(R.id.btnBuscar); lstResultados = (ListView)findViewById(R.id.lstResultados); } |
Mas como o Android vai saber qual informação deve ir em cada parte do nosso layout personalizado? Nós temos de dizer isso pra ele! O ArrayAdapter tradicional não consegue dar conta deste recado, por isso devemos criar o nosso Adapter personalizado, que vamos chamar de CidadeAdapter e que será uma subclasse do Adapter original.
Nesse Adapter personalizado, colocaremos a lógica de como fazer o binding dos campos de cada item da ListView conforme o layout XML que criamos para o mesmo. Segue abaixo um exemplo onde o método getView será disparado uma vez para cada item que adicionarmos na ListView, carregando a cidade em questão e adicionando cada um dos seus atributos ao widget correspondente no layout item_modelo.xml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package com.example.fernando.exemplolistview; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.List; public class CidadeAdapter extends ArrayAdapter<cidade> { private List<cidade> items; public CidadeAdapter(Context context, int textViewResourceId, List<cidade> items) { super(context, textViewResourceId, items); this.items = items; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { Context ctx = getContext(); LayoutInflater vi = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(R.layout.item_modelo, null); } Cidade cidade = items.get(position); if (cidade != null) { ((TextView) v.findViewById(R.id.lblCidade)).setText(cidade.getNome()); ((TextView) v.findViewById(R.id.lblUF)).setText("/" + cidade.getUf()); ((TextView) v.findViewById(R.id.lblDescricao)).setText(cidade.getDescricao()); ((ImageView) v.findViewById(R.id.imgCidade)).setImageResource(cidade.getIdImagem()); } return v; } } |
Agora devemos alterar o método que é disparado quando o nosso botão de buscar é pressionado (btnBuscar_OnClick), para que carregue um CidadeAdapter em nosso ListView, usando o nosso layout personalizado item_modelo.xml. O código abaixo mostra exatamente isso, já com a lógica de busca refatorada.
1 2 3 4 5 6 7 8 9 10 11 12 |
public void btnBuscar_OnClick(View v){ String busca = txtPesquisa.getText().toString(); List<cidade> encontradas = new ArrayList<>(); for(Cidade cidade : cidades){ if(cidade.getNome().contains(busca)) encontradas.add(cidade); } CidadeAdapter adapter = new CidadeAdapter(getBaseContext(), R.layout.item_modelo, encontradas); lstResultados.setAdapter(adapter); } |
Obtemos o seguinte resultado:
No evento de click de um item da lista, na hora que quisermos fazer alguma ação com base no objeto selecionado, teremos de fazer uma conversão um pouco diferente do toString usado geralmente, como segue no código abaixo que deve colocar logo após carregar a variável lstResultados, do tipo ListView, usando o findViewById:
1 2 3 4 5 6 7 8 |
lstResultados.setOnItemClickListener(new ListView.OnItemClickListener() { public void onItemClick(AdapterView<!--?--> parent, View view, int position, long id) { Cidade cidade = (Cidade)lstResultados.getItemAtPosition(position); Toast.makeText(getBaseContext(), "Item " + cidade.getNome(), Toast.LENGTH_LONG).show(); } }); |
Espero que tenham gostado do tutorial!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.
Fiz aqui e deu certinho. Show de bola.
https://uploads.disquscdn.com/images/8ecd6d28ca46b319e89e4c619ef1fe23cb8461c68710881ae5ceb7e5681906fd.jpg
http://media3.giphy.com/media/JltOMwYmi0VrO/giphy.gif