Crear ListView Básico en Android

Buenas tardes, en esta entrada vamos a ver una función básica de Android, muy usada, y que veremos muchas veces en muchas de las aplicaciones que usamos en el día a día, el ListView.


Los ListView no son mas que unos elementos que muestran items verticalmente en una lista con scroll, para poder ir moviendonos hacia abajo y ver todos los elementos. Veremos muchas implementaciones de este tipo de elementos en muchas aplicaciones de uso diario, como puede ser la aplicación de Twiter, WhatsApp.

  • Para comenzar vamos a crear un proyecto en Android.

    Rellenamos los datos como queramos, al fin y al cabo es una práctica y no hace falta que sea todo muy oficial. Ponemos mínimo el mínimo SDK requerido a la API 8 (Android 2.2), ya que aún queda algún terminal con Android 2.2, y siguen siendo usuarios. Como máximo API podemos poner la 4.1, ya que están abarcando el máximo mercado dentro de Android.

    En la siguiente pantalla elegiremos crear una Activity, he desmarcado la opción de crear un icono.

    En la pantalla siguiente, he elegido Blank Activity, no vamos a necesitar ninguna característica especial de las otras plantillas.

    En esta pantalla elegimos el nombre de la Actividad, lo que nos creará también el nombre del layout de la actividad nueva.

  • Con esto ya tendremos nuestra Activity creada. Ahora nos iremos al XML y añadiremos el ListView de esta manera, quedando el xml así:

    				<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"
    				    tools:context=".EjemploListView" >
    				    <ListView 
    				        android:id="@+id/listview"
    				        android:layout_width="wrap_content"
    				        android:layout_height="wrap_content"/>
    			
    				</RelativeLayout>
    				

    Con esto, si vamos a la parte gráfica del xml podremos ver ya algo así:

  • A continuación nos dirigimos a nuestro archivo java, ubicado en src/nombre_paquete/EjemploListView.java (en mi caso). Vamos a comenzar declarando el ListView y un array de String con unos elementos ya predefinidos, que serán los que usaremos para rellenar el ListView.

    				public class EjemploListView extends Activity {
    	
    					private ListView list;
    					private String[] sistemas = {"Ubuntu", "Android", "iOS", "Windows", "Mac OSX", 
    												"Google Chrome OS", "Debian", "Mandriva", "Solaris", "Unix"};
    				
  • Ahora nos meteremos dentro del método onCreate, para poder inicializar la lista, crear un Adapter, que es una clase la cual usaremos para rellenar el ListView y agregaremos alguna funcionalidad más al ListView.

    Para “enlazar” el ListView del xml con la variable de instancia que hemos creado, haremos lo siguiente:

    				list = (ListView)findViewById(R.id.listview);
    				

    Como vemos, usamos el método findViewById para buscar el id que le dimos al ListView en el xml.

    A continuación, vamos a declarar el Adapter. Esta clase será la encargada de adaptar el Array que tenemos a un ListView. En este caso, el adapter será solo de ya que nuestro array solo contiene elementos de tipo String.

    				ArrayAdapter<String> adaptador = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, sistemas);
    				

    Vamos a analizar bien el ArrayAdapter. Como bien indica el nombre de la clase, es un adaptador de arrays, y en este caso de tipo String.

    En su constructor, pasamos como argumentos los siguientes elementos:

    • Context: Pasamos el Context de la aplicación, mediante el uso de this.
    • android.R.layout.simple_list_item_1: Esto es el tipo de ListView, en nuestro caso, solo muestra un elemento, por lo tanto usaremos para cada dato un contenedor con un solo elemento.
    • sistemas: Le pasamos el array con los SO que hemos declarado como variable de instancia que está ubicado debajo del nombre de la clase.
  • Por último estableceremos el objeto adapter de la clase ArrayAdapter al ListView que hemos declarado, lo que hará que cargue los datos en el y ya podamos verlo en funcionamiento.

    				list.setAdapter(adaptador);
    				

    Como vemos es un proceso simple, en una sola línea.

  • Por último, vamos a agregar un listener a la lista, en este caso un onItemClickListener, que producirá un aviso mediante un objeto Toast con el número del elemento pulsado cuando pulsemos sobre la ListView. Esto podemos hacerlo de la siguiente manera.

    				list.setOnItemClickListener(new OnItemClickListener(){
    
    					@Override
    					public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) {
    						// TODO Auto-generated method stub
    						Toast.makeText(getApplicationContext(), "Ha pulsado el item " + position, Toast.LENGTH_SHORT).show();
    				
    					}
    			
    				});	
    				

    En el método generado, se pasan una serie de parámetros (esto se origina solo si usamos Eclipse). En este caso el que mas nos importa es el parámetro cuyo tipo de dato es int, al cual le he cambiado el nombre para que veamos bien a que se refiere.

    A continuación, creamos un Toast, el cual mostrará un aviso que nos devolverá el elemento que hemos pulsado.

Una vez hecho todo esto, podemos lanzar nuestra aplicación, y ver como ha quedado todo, a continuación pongo unas imágenes de como me ha quedado a mí y un pequeño video para verlo en acción:

Aquí el vídeo


Sin más, esto es todo, y es un elemento muy importante en Android, muy usado y que se puede personalizar muchísimo, lo cual veremos mas adelante con otros tutoriales.

Como siempre cualquier aporte o corrección es bienvenido, saludos!!!

  • Pingback: Cargar ListView con AsyncTask en Android « SekthDroid()

  • beto

    saludos, hay una manera de cargar nuevamente el adapter con nuevos datos sin que se borren los anteriores,
    si ya cargue 20 elemntos y los estoy mostrando , quiero que se carguen otros 20 pero sin borrar los primeros 20 e intentado de las siguientes formas pero no me deja y marca error
    for (int i=0;i<lstProductos.length;i++){
    adaptador.add(lstProductos[i]);
    }

    adaptador.addAll(lstProductos);

    las 2 formas me dan error
    }

    • Buenas tardes Beto, lo que debes hacer es añadir los 20 elementos al List, y una vez lo tengas, usar el método adaptador.notifyDataSetChanged(). Este método notifica que el contenido ha cambiado, y por tanto se refresca. Te pongo un ejemplo que acabo de hacer:

      public class MainActivity extends Activity {
      	private EditText txtTexto;
      	private Button btnAgregar;
      	private ListView listaItems;
      	private List<String> sistemas = new ArrayList<String>();
      
      	@Override
      	protected void onCreate(Bundle savedInstanceState) {
      		super.onCreate(savedInstanceState);
      		setContentView(R.layout.activity_main);
      
      		// Cargamos la List
      		sistemas.add("Windows");
      		sistemas.add("Ubuntu");
      		sistemas.add("Mac OSX");
      		sistemas.add("Solaris");
      
      		// Instanciamos las View
      		txtTexto = (EditText) findViewById(R.id.txtTexto);
      		btnAgregar = (Button) findViewById(R.id.btbAgregar);
      		listaItems = (ListView) findViewById(R.id.lista);
      
      		// Creamos el ArrayAdapter<String>
      		final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
      				MainActivity.this, android.R.layout.simple_list_item_1,
      				sistemas);
      
      		// Establecemos el Adapter
      		listaItems.setAdapter(adapter);
      
      		// Establecemos la funcionalidad del botón
      		btnAgregar.setOnClickListener(new OnClickListener() {
      
      			@Override
      			public void onClick(View v) {
      				// TODO Auto-generated method stub
      				if (txtTexto.getText().toString().length() == 0) {
      					// Si el EditText no contiene nada, mostramos un mensaje
      					Toast.makeText(getApplicationContext(),
      							"No ha introducido nada", Toast.LENGTH_SHORT)
      							.show();
      				} else {
      					// Cargamos mas elementos en la Lista
      					sistemas.add(txtTexto.getText().toString());
      					
      					// Notificamos que hay cambios
      					adapter.notifyDataSetChanged();
      				}
      			}
      
      		});
      
      	}
      
      }
      
      

      Como ves, he agregado elementos en el objeto de la clase List, no en el adapter (que tambien se podría). Y mediante el método, se agregan los nuevos elementos. Esto pasa porque el objeto de la clase ArrayAdapter sigue manteniendo una referencia al objeto de List que has usado para cargarlo previamente.

      Saludos!, espero que te sea de ayuda!!!

      PD: Puedes descargar el ejemplo aquí.

  • beto

    Saludos hay una forma de agregar 2 valores a la lista pero solo mostar uno por ejemplo cargar la lista con 2 campos (id_cte, nombre_cte), pero que en la lista solo se muestre el nombre y cuando yo seleccione un nombre leer el id_cte y ponerlo en unEdittext??
    es que lo hice pero utilizando 2 listas (una lista para cada uno de los campos) y cuando selecciono un nombre obtengo su posicion y la busco en la otra lista para poder obtenerlo

    es similar al valuemember y displaymember que utiliza VB.net

    • Una opción que podría serte válida es la de crear un “ListView” personalizado, el cual va a contener su propio BaseAdapter, el cual debes crear a mano, y también debes implementar sus métodos, uno de ellos te puede servir, ya que se llama getItemId, en el cual puedes implementarlo como quieras si no me equivoco.

      La idea sería crear algo como lo siguiente:

      package sekth.droid.lvPersonalizado;
      
      import java.util.List;
      
      import android.app.Activity;
      import android.content.Context;
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.BaseAdapter;
      import android.widget.TextView;
      
      public class MyAdapter extends BaseAdapter {
      	private List<Item> items;
      	private Activity activity;
      	
      	public MyAdapter(Activity activity, List<Item> items){
      		this.items = items;
      		this.activity = activity;
      	}
      
      	public static class ViewHolder {
      		public TextView id_cte;
      		public TextView nombre_cte;
      	}
      
      	@Override
      	public View getView(int position, View convertView, ViewGroup parent) {
      		// TODO Auto-generated method stub
      		View v = convertView;
      		ViewHolder holder;
      		if (v == null) {
      			LayoutInflater inflater = (LayoutInflater) activity
      					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      			v = inflater.inflate(R.layout.fila_cte, null);
      			holder = new ViewHolder();
      			holder.id_cte = (TextView) v.findViewById(R.id.tvIdCte);
      			holder.nombre_cte = (TextView) v.findViewById(R.id.tvNombreCte);
      			v.setTag(holder);
      		} else {
      			holder = (ViewHolder) v.getTag();
      		}
      
      		final Item item = items.get(position);
      		if (item != null) {
      			holder.id_cte.setText(String.valueOf(item.getId_cte()));
      			holder.nombre_cte.setText(item.getNombre_cte());
      		}
      
      		return v;
      	}
      
      	@Override
      	public int getCount() {
      		// TODO Auto-generated method stub
      		return items.size();
      	}
      
      	@Override
      	public Object getItem(int position) {
      		// TODO Auto-generated method stub
      		return items.get(position);
      	}
      
      	@Override
      	public long getItemId(int position) {
      		// TODO Auto-generated method stub
      		return items.get(position).getId_cte();
      	}
      
      }
      
      

      Si quieres ahora lo comento bien y te lo subo para que lo tengas 🙂

      Saludos!!!

    • Aquí te adjunto el código comentado para que te hagas una idea de como puedes hacerlo.

      Saludos y suerte, cualquier duda dímela 🙂

      • beto

        gracias espero me funcione con lo demas que estoy haciendo saludos

    • beto

      una duda como puedo hacer para qe el getitemid sea String?
      ya lo cambie y me da error??? y el cod_cte es string

      o solamente creo otro metodo qe me retorne un string??

      • Puedes crear un método en la clase MyAdapter que sea del tipo:

        public String getItemCod(int position){
        		return items.get(position).getCod_cte();
        	}
        

        Y con esto, en el onItemClickListener cambiar un poco:

        Toast.makeText(getBaseContext(), "Cod: " + adapter.getItemCod(position), Toast.LENGTH_SHORT).show();
        

        Otra cosa es que para hacerlo de esta manera, tienes que hacer que el objeto adapter sea final para poder ser accedido desde dentro.

        Saludos!!!

  • beto

    ya cargo la lista y el adapter pero cuando muestro la lista siempre si tengo 10 coincidencias con el texto me aparece 10 veces el ultimo cliente qe guarde en la lista asi:

    cliente 1 – 1
    cliente 1 – 1
    cliente 1 – 1
    cliente 1 – 1

    a qu se puede deber esto no se si en MyAdapter tenga qe hacer un ciclo for para guardar cada uno de los items de la lista, ya qe en el ejemplo que me diste colocaste un if
    saludos

    • A ver si entiendo exactamente lo que pasa:

      Introduces texto, y vas recibiendo coincidencias, esas coincidencias se van añadiendo al ListView, pero la última se agrega 10 veces seguidas,

      Por otro lado, la clase MyAdapter se ocupa de adaptar el contenido de un objeto de la clase List a un ListView, por tanto si lo que queremos es que las cosas salgan rápido, deberíamos quitarle trabajo al MyAdapter

      Lo mejor será meter un Log. para comprobar por donde puede estar dandose un problema, y vemos por donde falla.

      Saludos!!

      • beto

        perdon es que este comentario nolo avia visto!!!

        pero no sucede asi como lo explicas
        lo qe pasa es esto yo tengo todo guardado en la base de datos SQlite
        por ejemplo cuando escribo algo en el edittext se ejecuta mediante asynctask una consulta a la base de datos qe me trae todos los registros qe tengan coincidencia con el filtro qe mando (edittext), por decir qe encontro 10 coincidencias en total la consulta.
        entonces en el listview se muestra 10 veces el ultimo registro (solo me muestra este 10 veces), nada mas

        por lo qe los 10 registros qe muestro en el listview son el mismo

  • beto

    en esta parte del codigo:
    public View getView(….){
    ………..
    final Productos item = items.get(position);
    if (item != null) { <— Aqui no deberia ir un for qe recorra la lista items?????
    holder.codprod.setText(String.valueOf(item.getCodprod()));
    holder.descripcion.setText(item.getDescripcion());
    }
    return v;
    }

    aqui una imagen de com aparece
    https://www.dropbox.com/s/dzff17boqikd4hk/emulador.jpg

    • Jejeje no, el funcionamiento interno del Adapter es diferente:

      Pongamos que le pasas al adapter una lista de 20 objetos de la clase Producto por ejemplo.

      Lo que el adapter hace es llamar al método getView por cada objeto que exista en la lista, y devuelve la View que será una fila por cada elemento de la lista.

      -Tu has pasado una lista de 20 objetos.
      -El método getView se ejecuta tantas veces como objetos existan en la lista

      Ese bloque if lo que hace es comprobar que un objeto de la clase Producto que no haya sido instanciado se cuele y haga crash. Es decir evita que acciones como:

      Producto item = null;
      listaItems.add(item);
      
      • beto

        entonces a qe se puede deber qe me muestre el mismo producto varias veces????
        por medio del log revice la lista y si se envia con todos los valores

        • Para que me sea mas fácil consultarlo, haz lo siguiente:

          Bucle for que imprima en el log el producto cuando la lista se haya cargado (antes de establecer el adapter)

          Log antes del return en el getView con el producto que se está obteniendo

          Bucle for una vez que el adapter haya sido establecido de la lista que muestre el producto.

          A ver si así lo veo mejor 🙂

          Saludos!!!

          PD: Si quieres sube el log en archivo de texto al dropbox y ponme el link 🙂

  • beto

    aqui te dejo el link
    https://www.dropbox.com/s/jqy1biftw806y11/errores%20log%20android.txt

    despues de asignar la lista al adapter me da los errores repetidos

    • ¿Qué contiene la List después del setAdapter() (El objeto de la clase List, no el adapter en sí)?

      Saludos!

      • beto

        el set adapter lo realizo al inicio del programa en el onCreate
        y cada qe modifico la lista uso: adaptador.notifyDataSetChanged();
        para qe acepte los cambios (esto en esynctask)

      • beto

        no se si me entendiste???

        • Mas o menos x’D.

          Lo que intento hacer es reducir lo máximo el bloque de código para ver donde se añaden los elementos repetidos, ya que no es normal.

          Lo mejor será comprobar que datos hay en el List antes del adapter (que eso sale en el log), y los datos que hay en el List después de establecer el adapter, para ver si se han producido cambios dentro de el, y así centrar toda la atención en lo que esté pasando dentro de MyAdapter.

          Si los datos de la List antes de crear el MyAdapter son los mismos que después de crear el MyAdapter y establecerlo, quiere decir que se están añadiendo cosas que no deberían añadirse ahí dentro.

          Saludos!!

    • beto

      ya funciono gracias por la ayuda
      saludos

      • Veo que lo has solucionado, has averiguado lo que pasaba?

        Saludos!

      • beto

        la variable de producto no se inicializaba en cada vuelta del ciclo por eso se repetia solo el ultimo dato qe almacenaba, pero con lo anterior me sirvio para reducir el codigo
        saludos

  • hola que tal podrias darnos algun ejemplo o idea de como podemos hacer un listview pero con los datos de una base de datos en sqlite, espero tu respuesta gracias.

    • Buenas David,

      No hace mucho hice un tutorial de exactamente lo que me pides, lo puedes encontrar aquí

      Espero que te sirva de ayuda, y cualquier problema preguntame 🙂

      Saludos!!

  • beto

    hola tengo un adapter personalizado en el qe muestro diferentes detalles de un producto nombre, desc etc, ademas tengo un checkbox,
    con el check box lo que quiero es marcar los elemento qe deseo eliminar de la lista
    cuando presion el boton eliminar recorro la lista
    aqui esta lo que quiero saber
    como puedo obtener el valor del checkbox (checked =true/false) para eliminarlo de la base de datos

    este codigo es el qe tengo en el boton eliminar
    for(int i=0;i<lstDet.size();i++){
    if(lstDet.get(i).getEliminar() ){
    String cod_prod=lstDet.get(i).getCod_prod();
    db.delete("delete from mpedcte", "cod_prod="+cod_prod, null);
    }

    o como puedo hacerlo??
    saludo sy gracias

  • Buenas Beto,

    Una manera posible de hacerlo es tener una variable de instancia en la Clase del producto, que sea del tipo de dato boolean, y al cual le vayas aplicando true o false según vayas clickeando (y cambiando el valor del CheckBox).

    Con esto, podrías hacer una comprobación con un bucle for como bien haces.

    De otro modo, si tu aplicación está enfocada para un dispositivo con una API 11 o mayor, tienes la oportunidad de hacer que la ListView tenga múltiple elección:

    getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    

    Una vez esto, puedes llamar a un método de la siguiente manera:

    // Obtiene la cantidad de elementos que están seleccionados
    int count = getListView().getCheckedItemCount();
    				
    // Obtiene un array con los ID de los item seleccionados
    long[] checkedId = getListView().getCheckedItemIds();
    

    Como vés, este segundo método es bastante útil y fácil de implementar, así que sería cuestión de tu necesidad respecto al rango de dispositivos que quieras abarcar.

    Espero que te sea de ayuda.

    Saludos!

  • beto

    en la clase producto ya tengo declarada la propiedad de tipo boolean pero cuando pongo el checkbox como checked=true este no se actualiza en la clase por lo que siempre que entra en el ciclo me aparece como false por lo que nunca elimina nada

    • beto

      y como puedo agregar el evento onclick para cambiar el valor de la propiedad checked true/false
      esto seria en el adapter que cree para guardar las vistas

      • Según he estado leyendo sobre los CheckBox en distintos foros y páginas, hay un problema con los CheckBox, ya que no se guardan sus valores, y como en la ListView se van reciclando las listas, estos valores se ván perdiendo y me parece que salían casi todos true.

        Si puedes enseñarme la clase de tu Adapter, para ver como lo haces quizás te pueda ayudar mejor y así veo mejor el problema, o mediante algún link a DropBox.

        Saludos!!

  • beto

    aqui esta el adapter no se si este bien como lo quiero hacer o aiga una mejor forma de hacerlo
    quiero que el checkbox cambie cando se seleccione el checkbox o cuando se seleccione el elemento de la lista

    https://www.dropbox.com/s/su2o6syrrakuyzc/adapter.txt

  • Buenas Beto,

    He estado haciendo mis pruebas, y he logrado que funcione, no sé si es el mejor método para hacerlo, por lo que ya depende de tí ver si está bien o no 🙂

    Aquí tienes el link: https://dl.dropbox.com/u/64059675/ExampleCodes/MultipleCheckBoxExample.zip

    Notarás que en vez de usar BaseAdapter he usado ArrayAdapter para hacer el Adapter del ListView, pero es lo de menos.

    He declarado 2 métodos, uno que hace que se establezca el boolean del item, y a continuación se establezca el CheckBox y otro método que retorna una Lista con los ID de los elementos que han sido marcados.

    En el XML he quitado que el chekbox sea clickable y focusable, por tanto se activará o desactivará cuando se pulse cada fila del ListVIew.

    Espero que te sirva de ayuda, y vuelvo a decir que no sé si es el método mas eficaz de manejar este caso, y si tu app es para APIs superiores a la 11, puedes usar lo que te puse en un anterior comentario, que es bastante eficaz.

    Saludos!!

  • beto

    muchas gracias ya pude solucionar ese problema utilizando la clase base adapter
    saludos

  • Cesra

    Muchas Gracias me fue de mucha utilidad el tuto
    Saludos

  • Brandon Cardona

    Hola mira quierollenar un ListView con los datos que llegan desde una base de datos (Sqlite) Me podrias colaborar como lo haria? Muchas Gracias

    • Buenas Brandon,

      Justo tengo una entrada que habla sobre esto.

      Consiste en tener una base de datos SQLite y extraer sus datos para mostrar en un ListView, además de poder agregar nuevos elementos en la base de datos.

      Puedes consultarlo aquí.

      Espero que te sea de ayuda Cesar.

      Saludos!!

  • Ayarpm

    Ha sido una gran ayuda, muy simplificado y muy útil.

  • Andres

    Hola, tengo una duda, mira yo recibo datos de un WebService pero de dos metodos diferentes los 2 me regresan una lista en formato Json, lo que tengo que hacer es llenar una lista donde al principio me aparezcan los datos de un metodo y despues los datos de otro metodo en la misma lista

  • Excelente funciona a la perfección, Gracias!