ListView Custom

En esta entrada vamos a crear una Activity con una lista desplazable de nombres pero utilizando un layout a medida para cada item del ListView.

Creando el ListView

Creamos el ListView como en la entrada ListView Simple.

Creamos el Layout para cada item.

Ahora tenemos que crear un nuevo layout para nuestros items en la lista. Como vemos en la figura tenemos que crear un nuevo Layout Resource File

Creamos un Layout nuevo de tipo LinearLayout cuyo nombre será item_layout

Editamos el LinearLayout para ponerlo en “horizontal” y le añadimos una imagen y un texto. La imagen será fija para todos los items, de momento. Lo creamos como en la imagen.

La imagen la creamos como se indica en Icono y Up link en el Action Bar  (creando sólo la imagen) podéis utilizar cualquier icono.

Creando el Adaptador

Una vez creado el layout podemos ya crear un adaptador y vincularlo a nuestro item_layout. Creamos una nueva clase MyAdapter que hereda de BaseAdapter. Utilizamos new->Class en el package de nuestra MainActivity. Rellenamos el cuadro de diálogo para nuestra nueva clase como se muestra en la imagen.

La ayuda de edición nos indica que debemos implementar los métodos, pues BaseAdapter es una clase abstracta. Implementamos los métodos que nos ofrece por defecto, getCount(), getItem(), getItemId() y getView().

También deberemos crear un constructor para nuestra clase. Necesitaremos un contexto donde respresentar el layout, el id del layout que va a utilizar el adapter para cada item y la lista de elementos a gestionar por el adapter.

Por tanto el código de nuestro constructor y las variables miembro de nuestra clase será:

public class MyAdapter extends BaseAdapter {

    private int layout;
    private Context context;
    private List<String> items;
    

    public MyAdapter(Context context, int layout, List<String> items ){
        this.context = context;
        this.layout = layout;
        this.items = items;
    }
    

Cuando creemos un MyAdapter le pasaremos el contexto de la Activity donde se representará, el id del layout que hemos definido para los items y la lista de nombres.

Ahora hay que darle código a los métodos que hemos sobrecargado.

El método getCount() devolverá el número de elmentos que gestiona el Adapter, que en nuestro caso será el número de elementos que tiene el List<String>.

El método getItem(int position) recibe como parámetro un entero que es el número de elemento que nos piden. Debemos dar la posición del objeto, en nuestro caso el String que ocupa esa posición.

El método getItemId(int position) pide el id del elemento que se pasa. En nuestro List<String> no tenemos un id para cada string, devolveremos el mismo valor que nos pasan como position.

El método getView() recibe como parametros un int (la posición) una vista, y un viewgroup. La posición es la posición del elemento de en la Lista. La vista es un objeto que View que reutilizaremos, al que le cargaremos el layout para nuestros items y asignaremos las propiedades adecuadas para su representación, como el nombre y el icono, etc… El viewgroup es el propio ListView, que en nuestro caso podremos obviar.

Veamos primero el código y luego explicamos un poco más el método getView().

public class MyAdapter extends BaseAdapter {

    private int layout;
    private Context context;
    private List<String> items;


    public MyAdapter(Context context, int layout, List<String> items ){
        this.context = context;
        this.layout = layout;
        this.items = items;
    }


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

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

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

    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {
        View v;

        if (view==null){
            //Inflamos el layout de nuestros items en nuestra vista v
            LayoutInflater inflater = LayoutInflater.from(this.context);
            v = inflater.inflate(R.layout.item_layout,null);

        }
        //Partimos de la vista que nos pasa Android, que ya tiene cargado el layout.
        else v=view;

        //Obtenemos el nombre de la lista según la posición que nos pasan.
        String selectedName = items.get(position);

        //instanciamos el ImageView y el TextView de nestro Layout pars los items.
        TextView tvNombre = v.findViewById(R.id.tvNombre);
        ImageView ivPersona = v.findViewById(R.id.ivPersona);

        //El imageView podríamos buscar una imagen de la persona y colocarlo en vez de la imagen por defecto
        //Pero en este ejemplo eso no está implementado.

        //En el textView si ponemos el nombre.
        tvNombre.setText(selectedName);

        //Devolvemos la vista (nuestro layout) actualizada para la posición requerida.
        return v;
    }
}

Los adaptadores están pensados para reutilizar las vistas de los elementos que se quedan fuera del campo de visión del ListView, con lo que en tal caso nos pasarían un View para que lo reutilicemos. Si se trata de una de las vistas que están visibles en el ListView, conforme las va a ir “pintando” nos irá pasando null en el parámetro view, con lo que tendremos que darle la vista completa, es decir, tendremos que inflar el layout en la vista y modificar sus propiedades para que concuerde con el elemento (position) que nos pasan.

Android envía al adaptador la vista obsoleta para que no haga falta re-inflar el layout cuando ya lo ha hecho anteriormente, simplemente modificar sus propiedades para concordar con el position pasado.

Si por cualquier cosa necesitaramos un layout diferente para cada elemento, o sólo para algunos que cumplan algún requisito u estado, entonces tendremos que comprobar para cada uno e inflar siempre el layout, obviando el que nos pasa Android.

El LayoutInflater es un método que se obtiene del contexto, en este caso en el construtor obtenemos el contexto del Acitivity donde se va a mostrar la lista y nos lo guardamos como miembro de nuestro adaptador. Con el método inflater inflamos el layout que lo obtenemos de la clase R.

Instanciamos y Vinculamos nuestro Adapter

Ahora tenemos que crear un objeto de nuestra clase MyAdapter en el onCreate de la activity donde tengamos el listview y vincularlo.

        //Si utilizamos un MyAdapter hay que instanciarlo y vincularlo.
        MyAdapter myAdapter = new MyAdapter(this,R.layout.item_layout,nombres);
        listView.setAdapter(myAdapter);

Capturando el Click en el custom view

Lo único que nos queda es garantizar que el pulsar en un elemento de nuestra lista, es decir en cualquiera de nuestros custom views que la conforman, se pueda capturar el onItemClick en la lista.

Para ello debemos editar el custom layout, en nuestro caso el item_layout.xml y editar la propiedad descendatFocusability del root element, es decir del constraint layout o el linear layout que nos sirve de base para construir el layout.

Esta propiedad hay que ponerla a blockDescendants, para que no puedan tomar el foco de los clicks del usuario, y el click sea capturado para cada vista (custom layout) de nuestro listView, independientemente de sobre que View se produzca.

En cuanto al código para poder capturar el click podemos hacerlo de dos formas:

  • Capturando el OnItemClicck en en objeto listView en nuestra MainActivity
  • Capturando el OnClick en la ConvertView recibida en el método getView de nuestro Adapter.

Este sería el código necesario para la primera forma (iría en el MainActivity)

        //Ahora vinculamos el listView con el nuevo adapter
        listView.setAdapter(myAdapter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(MainActivity.this, "Pulsado en "+myAdapter.getItem(position), Toast.LENGTH_SHORT).show();
            }
        });

Este sería el código necesario para la segunda forma (iria en el MyAdapter)

convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(context, "view clicked: " + getItem(position), Toast.LENGTH_SHORT).show();
            }
        });

        //Devolvemos la vista (nuestro layout) actualizada para la posición requerida.
        return convertView;
    }