quarta-feira, 26 de dezembro de 2012

Android ExpadableListView

Olá povo,

Esse post estava no rascunho a algum tempo e resolvi publica-lo finalmente :) Vou mostrar como utilizar a lista agrupada do Android, a ExpandableListView. Como o próprio nome diz, ela contém uma lista de elementos que ao serem clicados exibem uma sublista. Como exemplo, teremos uma lista de estados, e ao clicar no nome do estado, exibiremos suas respectivas cidades.
Abaixo temos o código da Activity da aplicação.

public class ExemploExpladableListViewActivity
  extends Activity {

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

    ExpandableListView listview =(ExpandableListView)
      findViewById(R.id.expandableListView1);
   
    List<String> listPe = new ArrayList<String>();
    listPe.add("Caruaru");
    listPe.add("Recife");
   
    List<String> listSp = new ArrayList<String>();
    listSp.add("São Paulo");
    listSp.add("Campinas");
   
    Map<String, List<String>> dados = 
      new HashMap<String, List<String>>();
    dados.put("PE", listPe);
    dados.put("SP", listSp);
   
    listview.setAdapter(
      new MeuExpadableAdapter(dados));
  }
}

Como podemos notar, ao invés de usar uma lista simples, utilizamos um HashMap onde cada elemento será composto de uma chave que será o nome do estado e o valor será uma lista com as cidade daquele estado. O arquivo de layout contém apenas uma ExpandableListView, conforme abaixo.

<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/expandableListView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Como uma ListView comum, utilizaremos um Adapter para exibir o conteúdo da lista. A diferença é que herdaremos de BaseExpandableListAdapter, onde teremos que implementar mais alguns métodos além dos que já estamos acostumados nas listas convencionais:

  • getGroup: retorna o objeto que representa o grupo de informações. No nosso caso, a sigla do estado;
  • getGroupCount: retorna quantos grupos nós teremos. Aqui estamos retornando quantas entradas (chaves) temos na nossa HashMap;
  • getGroupId: algum inteiro que sirva de identificador único para o grupo, aqui estamos retornando a posição do grupo;
  • getGroupView: retorna a View que representará visualmente o grupo na lista;
  • getChild: similar ao getItem do Adapter convencional, a diferença é que é passado o índice do grupo e do elemento que se deve retornar;
  • getChildId: identificador único do elemento do grupo;
  • getChildView: retorna a View que representará o elemento dentro do grupo. Similar ao getView do adapter convencional;
  • getChildrenCount: retorna a quantidade de elementos dentro de um grupo;
  • hasStableIds: informa se os identificadores dos grupos e dos elementos são fixos;
  • isChildSelectable: retorna true se os elementos filhos podem ser selecionados, ou false caso contrário.

class MeuExpadableAdapter 
  extends BaseExpandableListAdapter {

  private Map<String, List<String>> dados; 
  private List<String> keys;
 
  public MeuExpadableAdapter(
    Map<String, List<String>> dados){

    this.dados = dados;
    this.keys = new ArrayList<String>(
      dados.keySet());
  }
 
  @Override
  public Object getGroup(int groupPosition) {
    return keys.get(groupPosition);
  }

  @Override
  public int getGroupCount() {
    return dados.size();
  }

  @Override
  public long getGroupId(int groupPosition) {
    return groupPosition;
  }

  @Override
  public View getGroupView(int groupPosition, 
    boolean isExpanded, View convertView, 
    ViewGroup parent) {

    View v = LayoutInflater.from(
      parent.getContext()).inflate(
        android.R.layout.simple_expandable_list_item_1, 
        null);

    TextView txt = (TextView)
      v.findViewById(android.R.id.text1);

    txt.setText(keys.get(groupPosition));
    return v;
  }
 
  @Override
  public Object getChild(
    int groupPosition, int childPosition) {

    return dados.get(
      keys.get(groupPosition)).get(childPosition);
  }

  @Override
  public long getChildId(
    int groupPosition, int childPosition) {
    return 0;
  }

  @Override
  public View getChildView(int groupPosition, 
    int childPosition, boolean isLastChild, 
    View convertView, ViewGroup parent) {

    View v = LayoutInflater.from(
      parent.getContext()).inflate(
        android.R.layout.simple_list_item_1, null);
    TextView txt = (TextView)
      v.findViewById(android.R.id.text1);
    txt.setText(dados.get(
      keys.get(groupPosition)).get(childPosition));
    return v;
  }
 
  @Override
  public int getChildrenCount(int groupPosition) {
    return dados.get(keys.get(groupPosition)).size();
  }

  @Override
  public boolean hasStableIds() {
   return true;
  }

  @Override
  public boolean isChildSelectable(
    int groupPosition, int childPosition) {
    return true;
  } 
}

O exemplo acima (com uma leve modificação :) está aqui.
Qualquer dúvida deixem seus comentários.

4br4ç05,
nglauber

8 comentários:

Henrique disse...

Fiz que nem os passos que tem aqui, mas minha Lista nao segue a mesma ordem que eu inseri, por conta disso, quando recupero a escolha da pessoa, eu seleciono um, e aparece é outro, é como se eu clica-se no 2º item e aparece-se o 3º item, me ajude, xD

Unknown disse...

tentei fazer a juncao do exemplo do segundo nivel fragment+ a listview de carros + o expandable do exemplo acima e nao sei onde erro pois o setAdapter nao é reconhecido no cod. comentei pra nao bugar a app. Onde estou errando nisso? Vc pode me ajudar?

//Teste com a ListView na FragmentSegundoNivel
ListView listView = (ListView) layout.findViewById(R.id.list);

//Test com Expandable
ExpandableListView expandlistView = (ExpandableListView)
layout.findViewById(R.id.expandableListView);

//COD BANCOS: 0=BB; 1=Bradesco; 3=CEF; 4=CitiBank; 5=HSBC; 6=Itau; 7=Santander
bancos = new ArrayList();

//BB
bancos.add(new Banco("Banco do Brasil", "www.bb.gov.br", 0));
List listBB = new ArrayList();
listBB.add("Ag. Centro");
listBB.add("Ag. Shopping Recife");
listBB.add("Ag. Shopping Riomar");
listBB.add("Ag. Boa Viagem");
listBB.add("Ag. Barão de Souza Leão");

//BRADESCO
bancos.add(new Banco("Bradesco", "www.bradesco.com.br", 1));
List listBradesco = new ArrayList();
listBradesco.add("Ag. Imbiribeira");
listBradesco.add("Ag. Shopping Guararapes");
listBradesco.add("Ag. Derby");

//BANCOS DIVERSOS
bancos.add(new Banco("Caixa Economica Federal", "www.caixa.gov.br", 2));
bancos.add(new Banco("CitiBank", "www.citi.com.br", 3));
bancos.add(new Banco("HSBC", "www.hsbc.com.br", 4));
bancos.add(new Banco("Itau", "www.itau.com.br", 5));

//SANTANDER
bancos.add(new Banco("Santander", "www.santander.com.br", 6));
List listSantander = new ArrayList();
listSantander.add("Ag. Cais do Apolo");
listSantander.add("Ag. Piedade");
listSantander.add("Ag. Antonio Falcão");

Map> dados = new HashMap>();
dados.put("Banco do Brasil", listBB);
dados.put("Bradesco", listBradesco);
dados.put("Santander", listSantander);

listView.setAdapter(new BancosAdapter(getActivity(), bancos));
// expandlistView.setAdapter(new BancoExpandableAdapter(getActivity(), dados));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

@Override
public void onItemClick(AdapterView adapterView, View view, int position, long id) {
Banco banco = (Banco) adapterView.getItemAtPosition(position);
// Toast.makeText(SegundoNivelFragment.this,
// banco.nomeBanco + ":" + banco.siteBanco, Toast.LENGTH_SHORT).show();

}

});
//Fim de teste com a listView

Unknown disse...

Tudo bem Glauber?!?!?!

Gostaria de saber se vc pode me ajudar a identificar onde erro nesse codigo que postarei logo abaixo. Tentei fazer a list do jeito q esta no seu livro do exemplo de carros que traz a imagem, a marca e talsz e tentei usar o exemplo do expandable pra criar grupos como se fosse grupo de Marcas Automotivas com os modelos de carros e fiz um grupo de bancos com o nome das agencias. Mas, qdo descomento o expandable ou ele da erro ou nao traz o grupo com imagem e talsz... Vc poderia me ajudar a decifrar esse enigma... tenho utilizado com muito exito seu livro, mas tem coisas que minha pouca capacidade nao me permite ir alem...

Obg e ansioso por novas postagens no seu site...

Abcs.

Nelson Glauber disse...

Olá Mooh Solution,

Esse código está disponível em algum lugar? Posso dar uma olhadinha nele...

4br4ç05,
nglauber

Unknown disse...

Olá Professor, tudo bem?

Gostaria de perguntar se é possível unir um Adaptador Customizado com o ExpadableListView ?

Caso sim, como ?

Tentei uni-los, mas sem sucesso :(


Agradeço pela atenção, até mais!

Nelson Glauber disse...

oi Webert,

Esse já é um adapter customizado. Você só precisaria mudar o seu layout...

4br4ç05,
nglauber

Blog do ueder disse...

E aí Nelson, artigo muito maneiro e muito útil. Mas me veio uma pequena dúvida, como eu faria pra ter um terceiro nível, estilo uma treeview ?

Mais ou menos assim:

1 - Nivel 1
1.1 - Nivel 2
1.2 - Nível 2
1.2.1 - Nivel 3
2 - Nivel 1
2.1 - Nivel 2

Nelson Glauber disse...

Oi Ueder,

Nesse caso, você teria que customizar o componente ou fazer o seu próprio.

4br4ç05,
nglauber