Las listas en Android son contenedores supremamente útiles para organizar información en forma vertical y con la capacidad de usar scrolling(desplazamiento) para simplificar su visualización. Esta técnica es muy popular en muchas aplicaciones, ya que permite mostrarle al usuario un conjunto de datos de forma practica y accesible.
Si sigues leyendo podrás aprender sobre:
- La clase ListView
- Crear una actividad basada en una lista
- Diseño en Android Studio
- Añadir datos desde un adaptador hacia tu lista
- Interacción ListView-Adapter
- La clase ArrayAdapter
- Crear tu propio origen de datos
- Sobrescribir la clase ArrayAdapter
- Poblando una lista
- Usar un
ImageView
en los elementos de la lista - Actualizar un ListView
- Manejar eventos con OnItemClickListener
- Personalizar el Selector de una Lista
- ¿Qué es un StateListDrawable?
- Crear un Selector para un ListView
La clase ListView
La clase que representa una lista vertical en el API de Android se llama ListView
. Esta clase viene preparada para recibir los items que desplegará en la interfaz, facilitando al programador la implementación de sus características y comportamientos. Si en algún momento los items que contiene dificultan la visualización total en la actividad de la aplicación, automáticamente implementará scrolling para que el usuario pueda desplegar los elementos ocultos.
Estructuralmente un ListView
contiene un View
especifico por cada fila. También se compone de un ScrollView
, el cual permite generar el desplazamiento vertical por si se agota la pantalla para nuestros elementos.
Crear una actividad basada en una lista
Para darle ruta a los conocimientos que veremos a lo largo de este articulo, vamos a planificar una miniaplicación que resuelva alguna necesidad en especial, donde necesitemos crear una lista.
En mi caso se me ha ocurrido crear una aplicación que guarde las "Cosas por hacer" de los usuarios. La idea es implementar una lista que guarde las tareas por hacer y su hora especifica de realización. Puedes descargar los recurso de la aplicación aquí:
A continuación veremos un bosquejo del diseño:
Diseño en Android Studio
Vayamos a la práctica. Lo primero es crear un nuevo proyecto en Android Studio llamado "TODO"
. Este tendrá una actividad en blanco y su nombre será Main
.
Debido a que un ListView
es un ViewGroup
, sería buena idea ponerlo como elemento raíz en el archivo de diseño de nuestra actividad (activity_main.xml
), ya que si lo incrustamos dentro de un RelativeLayout
u otro layout, se consumiría mayor tiempo de renderizado al procesar esta jerarquía.
Así que nuestro archivo de diseño quedaría de la siguiente forma:
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lista"/>
Como ves, el elemento <ListView>
es el equivalente de la clase ListView
y ha sido declarado como elemento raíz. Si observas sus dimensiones, le hemos asignado match_parent
para que se ajuste al tamaño de la actividad (actividad basada en una lista).
Echemos un vistazo a la previsualización:
Si ejecutamos nuestra aplicación con la anterior definición, observaríamos el siguiente resultado:
¿Qué sucedió?, ¿Por qué no están las filas que se mostraban en la previsualización?
La respuesta a esa inquietud esta en la siguiente sección...
Añadir datos desde un adaptador hacia tu lista
Un adaptador es un objeto que comunica a un ListView
los datos necesarios para crear las filas de la lista. Es decir, conecta la lista con una fuente de información como si se tratase de una adaptador de corriente que alimenta a un televisor. Además de proveer la información, también genera los Views para cada elemento de la lista. Mejor dicho, hace todo, solo que la lista es quién termina mostrando la información final.
Los adaptadores se representan programáticamente por la clase Adapter
. Dependiendo de la naturaleza de la lista se elegirá el tipo de adaptador. Existen subclases de la clase Adapter
proveídas por Android, que facilitan la mayoría de casos al poblar una lista. Pero si no satisfacen tus deseos, entonces puedes extenderlas para personalizar su comportamiento a tu gusto.
Interacción ListView-Adapter
Cuando referenciamos un adaptador a un List View inmediatamente comienza un proceso de comunicación interno para poblar la lista con los datos correspondientes. Dicha comunicación se basa principalmente en los siguientes métodos del adaptador :
getCount()
: Retorna en la cantidad de elementos que tiene un adaptador. Con este valor la lista ya puede establecer un limite para añadir filas.getItem()
: Obtiene un elemento de la fuente de datos asignada al adaptador en una posición establecida. Normalmente la fuente de datos es un arreglo o lista de objetos.getView()
: Retorna en el View elaborado e inflado de un elemento en una posición especifica.
Aunque estos tres métodos no son los únicos que existen para establecer la relación, a mi parecer son los mas significativos para entender el concepto de un adaptador.
Imagina este proceso como una conversación entre un gerente(ListView
) y su asistente(Adapter
).
L: ¿Cuantos elementos hay para hoy?
A: Hay "getCount()"
elementos Señor.
L: Comprendo...dejame ver el contenido del elemento número 3.
A: Si señor, es este "getItem(2)
"
L: amm...ya veo...¿y cual sería su View?
A: Al elemento numero tres le corresponde este View "getView(2)"
L: Muchas gracias, que fácil se hace el trabajo con tu ayuda.
Si aun no te ha quedado claro, entonces puedes ver esta ilustración sobre como sería esta relación:
La clase ArrayAdapter
Como habíamos dicho, Android nos provee subclases de adaptadores que nos facilitan la implementación. ArrayAdapter
es uno de los tipos de adaptadores mas sencillos y populares. Este permite construir listas en base a un array de elementos.
No importa de que tipo sea el array, este adaptador invoca el método toString()
de cada elemento y lo visualiza en un único TextView
.
Para implementar un ArrayAdapter en una lista simplemente seguimos los siguientes pasos:
Paso 1
Declarar las instancias de la lista y el adaptador:
ListView lista;
ArrayAdapter<String> adaptador;
...
Al declarar el adaptador describimos el tipo de objetos que manejará internamente, el anterior ejemplo tiene el tipo String
.
Paso 2
Obtener programáticamente una referencia de la lista:
lista = (ListView)findViewById(R.id.lista);
Paso 3
Inicializar el adaptador:
adaptador = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, arregloCadenas);
Donde los parámetros del constructor tienen el siguiente propósito:
Context context
: Representa el contexto de la aplicación. Usamosthis
para indicar que será la actividad que se basa en la creación de la lista.int resource
: Es el recurso de diseño o layout que representará cada fila de la lista. En este caso usamos un recurso del sistema llamadosimple_list_item_1.xml
. Este layout contiene un soloTextView
que contendrá el texto de cada fila.T[] objects
: Es la referencia del array de objetos de tipoT
con los cuales crearemos la lista. Si deseas puedes variar el constructor y usar una lista dinámicaList<T>
en lugar del array.
Paso 4
Y finalmente relacionar el adaptador con la lista:
lista.setAdapter(adaptador);
Como ves, usamos un método set()
para asignar el adaptador al atributo de la lista. Esto significa que desde el punto de vista de programación orientada a objetos, la clase ListView
se compone de un objeto Adapter
.
Crear tu propio origen de datos
Si la información que vas a usar en los elementos de la lista tiene formatos especiales o un manejo complejo, es mejor crear una nueva clase que represente a cada item. Esto permite mayor flexibilidad en el manejo de los datos.
La esencia del problema que se propuso al inicio nos habla de dos datos supremamente importantes por cada fila: El nombre de la tarea y la hora en que se realizará. Por lo que nos queda muy fácil traducir esa definición a una clase.
Veamos:
public class Tarea{
private String nombre;
private String hora;
public Tarea(String nombre, String hora){
this.nombre = nombre;
this.hora = hora;
}
public void setNombre(String nombre){
this.nombre = nombre;
}
public void setHora(String hora){
this.hora = hora;}
public String getNombre(){return nombre;}
public String getHora(){return hora;}
}
Hasta ahora todo muy bien. Pero existe un problema. Si recuerdas, el adaptador implementaba un layout con un solo TextView, lo cual no se ajusta a nuestros requerimientos, ya que el diseño a realizar es completamente distinto.
La solución a este problema es usar el siguiente recurso del sistema.
adaptador = new ArrayAdapter<String>(this,android.R.layout.two_line_list_item, arregloCadenas);
Este layout permite ubicar dos strings en cada fila, representando un item y un subitem. Esto es perfecto por que nosotros tenemos que mostrar el nombre y la hora de una tarea.:
Pero aun falta un factor que resolver. ArrayAdapter
dentro de su método getView()
llama al método toString()
de cada objeto de nuestro origen de datos. Si dejásemos nuestra clase Tarea
con el anterior aspecto, heredaría el método de la clase Object
y tendríamos una serie de nombres muy raros, estilo "Tarea@123422"
y eso no es lo que queremos.
Lo que se necesita es decirle al adaptador que vamos a enviar dos cadenas (nombre y hora) para que se ubiquen en el View que se inflará. Y la única manera de realizar esto es sobrescribiendo el método getView()
.
Sobrescribir la clase ArrayAdapter
El objetivo es claro, crearemos una nueva subclase llamada TareaArrayAdapter
para cambiar unicamente el comportamiento del método getView()
, quién es el que infla los views de nuestras filas. De resto todo lo demás lo dejamos igual.
package TUPAQUETE.todo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.List;
public class TareaArrayAdapter<T> extends ArrayAdapter<T> {
public TareaArrayAdapter(Context context, List<T> objects) {
super(context, 0, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
//Obteniendo una instancia del inflater
LayoutInflater inflater = (LayoutInflater)getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Salvando la referencia del View de la fila
View listItemView = convertView;
//Comprobando si el View no existe
if (null == convertView) {
//Si no existe, entonces inflarlo con two_line_list_item.xml
listItemView = inflater.inflate(
android.R.layout.two_line_list_item,
parent,
false);
}
//Obteniendo instancias de los text views
TextView titulo = (TextView)listItemView.findViewById(android.R.id.text1);
TextView subtitulo = (TextView)listItemView.findViewById(android.R.id.text2);
//Obteniendo instancia de la Tarea en la posición actual
T item = (T)getItem(position);
//Dividir la cadena en Nombre y Hora
String cadenaBruta;
String subCadenas [];
String delimitador = ",";
cadenaBruta = item.toString();
subCadenas = cadenaBruta.split(delimitador,2);
titulo.setText(subCadenas[0]);
subtitulo.setText(subCadenas[1]);
//Devolver al ListView la fila creada
return listItemView;
}
}
El código es muy intuitivo. Si te fijas en el constructor, lo hemos creado para que reciba dos parámetros el contexto y una lista generalizada. No necesitamos la referencia del layout, ya que sabemos que esta subclase solo se especializa con listas dos text views, por eso al llamar a super
asignamos cero a este parámetro.
Ahora comprendamos los parámetros de getView()
:
position
: Es la posición del item que deseamos inflar en la lista.convertView
: Representa la instancia donde asignaremos el View creado. La primera ves que es llamado para un elemento especifico su valor seránull
, debido a que aun no existe el view en la lista, pero una vez es haya sido creado debemos evitar volver a inflarlo, ya que esto consumiría memoria extra.parent
: View padre donde se asignará nuestro item, en este caso será elListView
.
Las primeras actividades tienen que ver con el inflado del código xml que existe en el archivo de recurso two_line_list_item.xml
, algo que no es desconocido si has seguido los artículos de Hermosa programación.
Luego obtenemos instancias de los Text Views. El text view superior tiene el id text1
y el inferior text2
.
Y Finalmente llegamos a la parte distinta, donde hemos realizado una tarea de separación de cadenas. Lo primero que hacemos es capturar el resultado del método toString()
de nuestro objeto Tarea
. En seguida partimos la cadena en 2 partes con el método split()
cuando se encuentre el delimitador ",
". Luego asignamos la primer parte al TextView
titulo y la segunda a subtitulo.
Ahora veamos el método toString()
de Tarea que construimos:
@Override
public String toString(){return nombre+","+hora;}
Como ves, se retorna en una cadena compuesta por el nombre y la hora. Ambas separadas por una coma.
Obviamente esta convención no es obligatoria. Existen infinidad de maneras para obtener las dos cadenas por separado, todo depende de tu imaginación y practicas de programación.
Poblando una lista
Lo ultimo que falta es generar las tareas que añadiremos a nuestro a adaptador. Para ello crearemos una nueva clase estática que represente el ambiente de intercambio de datos entre el adaptador y los objetos tipo Tarea:
package TUPAQUETE.todo;
import java.util.ArrayList;
import java.util.List;
public class DataSource {
static List TAREAS = new ArrayList<Tarea>();
static{
TAREAS.add(new Tarea("Trotar 30 minutos","08:00"));
TAREAS.add(new Tarea("Estudiar análisis técnico","10:00"));
TAREAS.add(new Tarea("Comer 4 rebanadas de manzana","10:30"));
TAREAS.add(new Tarea("Asistir al taller de programación gráfica","15:45"));
TAREAS.add(new Tarea("Consignarle a Marta","18:00"));
}
}
El nombre de esta nueva clase es DataSource
y su fin es representar las fuentes de datos a las que las demás clases pueden acceder. Como ves creamos una lista estática de objetos Tarea
, la cual inicializamos con 5 objetos. Con esta implementación podemos independizar completamente la vista de los datos y acceder con flexibilidad a todo tipo de recursos de información.
Así que implementaremos todo lo visto hasta aquí en la actividad Main
de la siguiente forma:
package TUPAQUETE.todo;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class Main extends Activity {
ListView lista;
ArrayAdapter adaptador;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Instancia del ListView
lista = (ListView)findViewById(R.id.lista);
//Inicializar el adaptador con la fuente de datos
adaptador = new TareaArrayAdapter<Tarea>(
this,
DataSource.TAREAS);
//Relacionando la lista con el adaptador
lista.setAdapter(adaptador);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
Ejecuta y verás el siguiente resultado:
Cabe destacar que estamos probando un ejemplo estático, en algunos casos la cantidad de elementos de la lista cambia en tiempo real, por lo que debemos ajustar el código para ese tipo de situaciones. También es importante resaltar que podemos crear cualquier tipo de View para las filas, la cuestión está en extender la clase ArrayAdapter
para que se acople a tus necesidades.
Usar un ImageView
en los elementos de la lista
Supongamos que deseamos extender el uso de la anterior aplicación, donde asignaremos a cada item una categoría representada por un icono alusivo. El bosquejo para esta idea se vería mas o menos así:
Esta vez ya no podemos apoyarnos en los recursos de Android , ya que no existe un layout previamente diseñado que permita insertar una imagen en un elemento. Por esta razón crearemos nuestro propio archivo de diseño.
Para ello crea un nuevo recurso llamado image_list_item
con un nodo raíz RelativeLayout
e implementa las posiciones que vimos en el boceto anterior.
La descripción quedaría mas o menos así:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:padding="5dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/category"
android:layout_marginRight="10dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/text1"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/category"
android:textAppearance="@android:style/TextAppearance.Holo.Large"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/text2"
android:layout_below="@+id/text1"
android:layout_toRightOf="@+id/category"
android:layout_alignParentBottom="true" />
</RelativeLayout>
Usaremos los siguientes tres iconos para representar las siguientes categorías: Salud (ic_health
), Finanzas (ic_money
) y Carrera (ic_carreer
):
Todos ellos fueron descargados de un excelente portal llamado IconFinder. Elegí un tamaño de 48x48 ya que usaré un emulador mdpi.
Ahora, el siguiente paso es asignar una categoría a cada tarea. Para ello crearemos un nuevo atributo llamado categoría
en nuestra clase Tarea
. En él asignaremos la referencia del icono correspondiente a la categoría.
Así que la clase Tarea
quedaría de la siguiente forma:
package TUPAQUETE.todo;
public class Tarea{
private String nombre;
private String hora;
private int categoria;
public Tarea(String nombre, String hora, int categoria){
this.nombre = nombre;
this.hora = hora;
this.categoria = categoria;
}
public void setNombre(String nombre){
this.nombre = nombre;
}
public void setHora(String hora){
this.hora = hora;
}
public void setCategoria(int categoria){
this.categoria=categoria;
}
public String getNombre(){return nombre;}
public String getHora(){return hora;}
public int getCategoria(){return categoria;}
}
Seguidamente, actualizaremos el método getView()
del adaptador. Básicamente solo debemos agregar las instrucciones para obtener la instancia del ImageView
y asignarle el drawable correspondiente con setImageResource()
:
package TODO.todo;
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 TareaArrayAdapter extends ArrayAdapter<Tarea> {
public TareaArrayAdapter(Context context, List<Tarea> objects) {
super(context, 0, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
//Obteniendo una instancia del inflater
LayoutInflater inflater = (LayoutInflater)getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Salvando la referencia del View de la fila
View listItemView = convertView;
//Comprobando si el View no existe
if (null == convertView) {
//Si no existe, entonces inflarlo con image_list_view.xml
listItemView = inflater.inflate(
R.layout.image_list_item,
parent,
false);
}
//Obteniendo instancias de los elementos
TextView titulo = (TextView)listItemView.findViewById(R.id.text1);
TextView subtitulo = (TextView)listItemView.findViewById(R.id.text2);
ImageView categoria = (ImageView)listItemView.findViewById(R.id.category);
//Obteniendo instancia de la Tarea en la posición actual
Tarea item = getItem(position);
titulo.setText(item.getNombre());
subtitulo.setText(item.getHora());
categoria.setImageResource(item.getCategoria());
//Devolver al ListView la fila creada
return listItemView;
}
}
Ahora solo queda por cambiar la inicialización estática de la fuente de información. Asignaremos los ids de cada drawable correspondiente:
static{
TAREAS.add(new Tarea("Trotar 30 minutos","08:00",R.drawable.ic_health));
TAREAS.add(new Tarea("Estudiar análisis técnico","10:00",R.drawable.ic_money));
TAREAS.add(new Tarea("Comer 4 rebanadas de manzana","10:30",R.drawable.ic_health));
TAREAS.add(new Tarea("Asistir al taller de programación gráfica","15:45",R.drawable.ic_carreer));
TAREAS.add(new Tarea("Consignarle a Marta","18:00",R.drawable.ic_money));
}
Al ejecutar el proyecto verías lo siguiente:
Actualizar un ListView
Si en algún momento los datos de la lista cambian, el encargado de actualizar los elementos será el adaptador. El método que informa dichas modificaciones es notifyDataSetChanged()
. Cuando se llama, los views son refrescados y así el usuario percibe el cambio en tiempo real.
Este método es ejecutado automáticamente por el adaptador cuando se llaman métodos que modifiquen la fuente de datos. Alguno son:
-
add()
: Añade un nuevo elemento al final de la lista. insert()
: Añade un nuevo elemento en una posición especificada de la lista.remove()
: Elimina un elemento de la listaclear()
: Elimina todos los elementos de la lista.
Por ejemplo. Podemos añadir un Action Button a la Action Bar llamado "Limpiar
", el cual tendrá como fin limpiar todos los elementos de la lista a través del método clear()
del adaptador. Implementemos estas instrucciones en onOptionsItemSelected()
:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_clear) {
//Limpiar todos los elementos
adaptador.clear();
return true;
}
return super.onOptionsItemSelected(item);
}
Al probar veremos que automáticamente los elementos de la lista han sido erradicados:
Manejar eventos con OnItemClickListener
Si deseas ejecutar acciones cuando el usuario selecciona algun item de la lista, entonces debes setear una escucha del tipo OnItemClickListener
. Esta interfaz permite disparar el método onItemClick()
, el cual nos permitirá ejecutar las acciones que deseamos automatizar.
//Estableciendo la escucha
lista.setOnItemClickListener(tuEscucha);
A continuación crearremos una simple interacción cuando se presionen las tareas que tenemos en la aplicación TODO
. La idea es proyectar un aviso que nos informe en pantalla los datos del elemento que se presionó.
Veamos:
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Tarea tareaActual = (Tarea)adaptador.getItem(position);
String msg = "Elegiste la tarea:\n"+tareaActual.getNombre()+"-"+tareaActual.getHora();
Toast.makeText(this,msg,Toast.LENGTH_LONG).show();
}
onItemClick()
recibe tres parámetros. El primero es el View que usa al adaptador, en este caso la lista. El segundo es el View del item que ha sido presionado, el tercero hace referencia a la posición del item en la fuente de datos que maneja el adaptador y el cuarto es un identificador del elemento.
En esta ocasión simplemente obtuvimos el item Tarea
presionado en el adaptador con la posición de entrada. Luego de ello construimos una cadena que indique el nombre y la fecha del elemento seleccionado. Finalmente desplegamos en pantalla un Toast con los datos:
Nota: Un elemento Toast
es una pequeña ventana emergente con una duración determinada, el cual despliega un mensaje para notificar al usuario el cumplimiento de alguna acción. Su método makeText()
genera una instancia prefabricada, la cual posee un solo TextView
, esto hace que no se requiera crear una instancia del Toast ni inflar su contenido con un archivo de diseño.
Personalizar el Selector de una Lista
¿Qué es un StateListDrawable?
Los fondos que se proyectan en un View al interactuar con la interfaz de usuario, son representados por un objeto drawable llamado StateListDrawable
. Este esta compuesto por una serie de drawables relacionados a un estado de interacción del componente.
¿Has visto que los items de un Listview
toman un color azul al ser presionados?, cuando esta iluminado se le llama estado de Presionado(pressed
). Ese fondo que toma momentaneamente el item es generado a través de un StateListDrawable
predeterminado por los temas y estilos que estemos utilizando. Existen otros estados como "Presionado sostenidamente"(long pressed), Enfocado (focusabled), Seleccionado(selected), etc.
Crear un Selector para un ListView
Para declarar previamente el StateListDrawable
de un view antes de inflarlo, podemos usar un archivo de recursos de tipo drawable
. Donde el nodo que relacionará las imágenes con la lista de estados es representado por el elemento <selector>
. Dentro de él declararemos elementos <item>
que contendrán la información sobre cada drawable.
Veamos un ejemplo:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/list_pressed" /><!-- Pressed -->
</selector>
La anterior descripción asigna un drawable al estado stated_pressed
(presionado). Para habilitar la transición de fondo, el estado debe tener el valor de true
y si deseas deshabilitarlos entonces utiliza false
. En el atributo drawable
puedes usar una referencia a tus recursos. La práctica común es crear NinePatches para la flexibilidad del fondo en cualquier tipo de dimensiones y densidades.
Una vez creada tu lista de estados, pasamos a asignarla al ListView
. Para ello usamos el atributo listSelector
, el cual representa la lista de estados de nuestro View.
De modo que procedemos a asignar el selector en nuestra layout:
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lista"
android:listSelector="@drawable/item_list_indicator"/>
Y el resultado final de nuestra aplicación TODO
sería el siguiente: