«

»

may 06 2012

Usando ExpandableListView en Android

Android nos brinda con múltiples vista que nos facilitan la vida a la hora de diseñar e implementar las interfaces de nuestras apps, entre ellas se encuentra la que pasamos a enseñaros hoy y que nos proporciona una manera elegante de mostrar listados de datos agrupados de una forma ordenada.

Normalmente usaremos un ListView para mostrar los datos pero si la cantidad de estos es excesiva y tenemos la posibilidad de agruparlos mediante algún criterio una opción muy a tener en cuenta seria usar ExpandableListView que es la vista que a continuación os vamos a enseñar a usar:

 

 

Nota: En este tutorial suponemos unos conocimientos básicos de desarrollo en Android y manejo de layouts mediante xml.

 

 

PASO 1 – Inicializamos el ExpandableListView y ExpandableListAdapter en el onCreate()

 

Una vez hemos incluido nuestro ExpandableListView en nuestro xml de definición de Layout y le hemos asociado el un id, mediante código instanciaremos el Adapter y los asociaremos al mismo:

ExpandableListAdapter mAdapter;
ExpandableListView epView = (ExpandableListView) indViewById(R.id.ExpandableListView01);
mAdapter = new MyExpandableListAdapter();
epView.setAdapter(mAdapter);

 

PASO 2 – Creamos nuestro Adapter


Al igual que ListView y cualquier AdapterView deberemos usar un adapter que gestione la colección de datos que mostraremos, aquí os dejamos un ejemplo de uno:

/**
     * A simple adapter which maintains an ArrayList of photo resource Ids. Each
     * photo is displayed as an image. This adapter supports clearing the list
     * of photos and adding a new photo.
     *
     */
    public class MyExpandableListAdapter extends BaseExpandableListAdapter {
    // Sample data set. children[i] contains the children (String[]) for
    // groups[i].
    private String[] groups = { "Parent1", "Parent2",
        "Parent3" };
    private String[][] children = { { "Child1" },{ "Child2" }, { "Child3" },{ "Child4" }, { "Child5" } };

    public Object getChild(int groupPosition, int childPosition) {
        return children[groupPosition][childPosition];
    }

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

    public int getChildrenCount(int groupPosition) {
        int i = 0;
        try {
        i = children[groupPosition].length;

        } catch (Exception e) {
        }

        return i;
    }

    public TextView getGenericView() {
        // Layout parameters for the ExpandableListView
        AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
            ViewGroup.LayoutParams.FILL_PARENT, 64);

        TextView textView = new TextView(MainActivity.this);
        textView.setLayoutParams(lp);
        // Center the text vertically
        textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
        textView.setTextColor(R.color.marcyred);
        // Set the text starting position
        textView.setPadding(36, 0, 0, 0);
        return textView;
    }

    public View getChildView(int groupPosition, int childPosition,
        boolean isLastChild, View convertView, ViewGroup parent) {
        TextView textView = getGenericView();
        textView.setText(getChild(groupPosition, childPosition).toString());
        return textView;
    }

    public Object getGroup(int groupPosition) {
        return groups[groupPosition];
    }

    public int getGroupCount() {
        return groups.length;
    }

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

    public View getGroupView(int groupPosition, boolean isExpanded,
        View convertView, ViewGroup parent) {
        TextView textView = getGenericView();
        textView.setText(getGroup(groupPosition).toString());
        return textView;
    }

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

    public boolean hasStableIds() {
        return true;
    }

    }

   

En este ejemplo generamos las vistas tanto del Parent como del Child mediante código, pero no habría ningún problema en generarlas a partir de archivos xml. Aquí un ejemplo:

public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        View rowparent = View.inflate(ctx, R.layout.rowparent, null);
        TextView nameTV = (TextView) rowparent.findViewById(R.id.name);
        
        nameTV.setText(countryName);
        
        // Nota: El LayoutParams ira en funcion del contenedor global elegido en nuestro Layout
        // Al inflar codigo alguna de las configuraciones del xml se pierden y se tendran que indicar mediante codigo, como es el caso siguiente.
        AbsListView.LayoutParams lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, rowHeight);
        nameTV.setLayoutParams(lp);

        return nameTV;
    }

 

PASO 3 – Listeners


Por último implementaremos los métodos que añadirán la funcionalidad a los elementos del listado:

epView.setOnGroupClickListener(new OnGroupClickListener() {
        @Override
        public boolean onGroupClick(ExpandableListView arg0, View arg1,
            int groupPosition, long arg3) {
        if (groupPosition == 5) {               

        }

        // Aqui podriamos cambiar si quisieramos el comportamiento de apertura y cierre de las listas explandibles mediante los metodos collapseGroup(int groupPos) y expandGroup(int groupPos)

        return false;
        }
        });

    epView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
    @Override
    public boolean onChildClick(ExpandableListView parent,
            View v, int groupPosition, int childPosition,
            long id) {
        if (groupPosition == 0 && childPosition == 0) {
           
        }

        return false;
    }
    });

 

PASO 4 – Personalización


Mediante xml podemos configurar ciertos aspectos gráficos como los divisores del listado o los indicadores. Podéis ver el listado completo en la documentación oficial. A continuación os contamos como crear nuestro propio groupIndicator.

 

Si no quisiéramos incluir ningún groupIndicator bastaría con indicarlo en el propio xml de la siguiente manera:

android:groupIndicator="@null"

 

En caso contario nos crearíamos nuestro propio drawable que referenciaríamos en el mismo atributo. Aquí un ejemplo de la estructura que debería de seguir el drawable:

<selectorxmlns:android="http://schemas.android.com/apk/res/android">
    <itemandroid:state_empty="true"android:drawable="@android:color/transparent"/>
    <itemandroid:state_expanded="true"android:drawable="@drawable/my_icon_max"/>
    <itemandroid:drawable="@drawable/my_icon_min"/>
</selector>

 

Y con esto hemos terminado, con un poco de maña e imaginación se pueden conseguir resultados muy vistosos como el que os mostramos de nuestra próximas app:

 

 

 

 

vía | Ingens Blog

Acerca del autor

JMPergar

Mobile Developer at @BeRepublic & Founder of @AndroCode. Silver Speaker & Member of Core Team at @GDGBarcelona.

  • Aaron

    Que buen tutorial, una pregunta si hice mi propio xml para personalizar el Children de hecho son dos y tambien cree uno para el Group, como puedo hacer para que los children aparezcan con el layout que yo indique, osea que el q esta en la posicion 0 tenga el layout1 el que este en la posicion 1,2,3,4 tenga el layout 2, el que este en la posicion 5 que tenga el layout 1, espero haberme explicado

    • http://www.linkedin.com/in/jmpergar JMPergar

      jeje pues no, creo que no te acabo de entender… Si lo que quieres es que en funcion de la posicion tengan distintos layouts esto lo gestionas todo desde los getGroupView y getChildrenView cargando uno u otro en función de tus necesidades.

  • Rafa

    Gracias por el aporte. Tengo una duda:
    ¿Cómo podría coger el texto del hijo en el evento onChildClick? Quiero usarlo para llamar Intent.ACTION_DIAL ya que tengo implementada una vista ExpandibleList con los contacto donde cada hijo es el nº de teléfono. Y quiero coger el número para pasarlo a ACTION_DIAL y no doy con la tecla.
    Gracias

  • Rafa

    Me autocontesto. Es una chorrada, como todo, cuando se descubre.
    Para tomar el valor (texto) del hijo en onChildClick

    @Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {

    TextView tw = (TextView) v.findViewById(android.R.id.text1);
    Toast.makeText(getApplicationContext(),tw.getText(),Toast.LENGTH_LONG).show();

    }

    • Willian

      Hola, una preguntita Rafa, si ud toma de sus contactos los datos significa que la lista con sus hijos es dinámica es decir en una instancia puden ser 3 items con 2 subitems cada uno o 5 items con 3 items cada uno, etc etc como seria el adaptador para asignarselo al ExpandibleListView o obligatoriamente hay que hacerlo item por item….

  • kiduxa

    Es ExpandableListView un buen sustituto de ListView dentro de un scroll view?

  • Pablo

    Buenas,

    me acabo de iniciar en esta materia y tengo un problema que me trae de cabeza desde hace semanas. Implemento una expandablelistview que toma datos de una bd, los cuales recupera bien, pero a la hora de crear la lista tan solo me muestra el primer elemento de la misma. He introducido un toast en la función getGroupView del ExpandableListAdapter y resulta que groupPosition siempre es 0.

    No entiendo la razón, ya que el ArrayList de grupos, como ya he dicho, se rellena correctamente con todos los grupos existentes. Luego defino el ExpandableListAdapter con la clase y este ArrayList y luego llamo al setAdapter sobre el ExpandableListView.

    ¿Alguna idea?

    Gracias.

  • Gilberto

    una Pregunta… estaba haciendo unos ListView con Custom Adapter y un Holder.. que segun es mas rapido para la reutilizacion de vistas…

    Es posible meter ese holder, aqui?… o mas bien, seria los mas correcto meterle un Custom Adapter y un holder siempre para optimizacion?

  • Aldair

    Hola.. excelente tutorial… pero tengo una pregunta..
    Se puede incluir un expandableListView en un fragment para ser utilizado en un navigation drawer..?? Espero tu respuesta y de antemano gracias..
    Saludos…!! ;)