Los Spinners son menús desplegables en Android que permiten al usuario decidirse por un camino entre varias opciones.
De seguro alguna vez habrás visto un formulario en una aplicación donde te pidan elegir tu ciudad de una lista o cuando te permiten cambiar la temporalidad de los resultados proyectados desde un menú en la Action Bar.
Ambos casos son ejemplos de Spinners y espero que este tutorial te sirva de guía para poder usarlos. Sigue leyendo y podrás descargar un ejemplo completo para interiorizar los conocimientos.
¿Qué es un Spinner?
Un Spinner es un View que despliega una lista de elementos que están expectantes a la selección del usuario. Dependiendo del elemento seleccionado nuestra aplicación tomará las decisiones pertinentes para seguir el flujo de ejecución.
El concepto es sencillo y he preparado un pequeño ejemplo para que puedas interiorizarlo. Se trata de una pequeña aplicación llamada Gamerty, que permite seleccionar la guía del videojuego que elija el usuario del Spinner. Veamos como se ve:
Para desbloquear el link de descarga del código completo, sigue estas instrucciones:
[sociallocker id=»7121″][/sociallocker]
Ahora procedamos a recrear su elaboración…
Ubicar un Spinner en Android Studio
El primero paso es la creación de un nuevo proyecto en Android Studio con una actividad principal, a la cual le llamarás Main. Inmediatamente vas a su archivo de diseño (activity_main.xml) y añades desde la ventana Pallete el Spinner.
Le asignarás el identificador «GameSpinner», lo centras horizontalmente (centerHorizontal) y luego lo haces limitar con el borde superior del padre (alignParentTop).
Si todo te ha salido excelente, tendrías el siguiente archivo de diseño:
<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" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".Main"> <Spinner android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/GameSpinner" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" /> </RelativeLayout>
Crear un Origen de Datos
Al igual que cuando vimos <?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Strings generales -->
<string name="app_name">Gamerty</string>
<!-- Conjunto de Strings para el View GameSpinner -->
<string-array name="Games">
<item>League of Legends</item>
<item>Diablo III</item>
<item>God of War</item>
<item>Halo 4</item>
<item>StarCraft II</item>
</string-array>
</resources>
Si te fijas en la definición, verás un elemento <string-array> con nombre «Games». Si aún no lo sabes, este elemento representa un array de strings (como su nombre lo indica). Dentro de él debes incluir cada dato como un nodo <item>, cuyo contenido es la cadena. Una vez hayas definido tu origen de datos vas a crear un adaptador que se ajuste a tus necesidades. En el caso de Gamerty se usa un ArrayAdapter con elementos simples, ya que solo necesitas proyectar el nombre del juego. Para ello se obtiene la instancia del Spinner, luego declaras el adaptador, lo inicializas en el método onCreate() de la actividad Main y finalmente los relacionas: El código anterior muestra el uso del método de clase createFromResource(), el cual nos permite crear un nuevo ArrayAdapter a partir de los recursos string que están en el proyecto. El primer parámetro que recibe es el contexto, en este caso es la actividad actual, luego la referencia del <array-string> que declaraste y por último el layout para inflar cada ítem, donde se usa el recurso del sistema simple_spinner_item.xml, que representa un TextView sencillo. Luego usamos el método del adaptador Tomaremos el recurso de la plataforma Este tipo de text view hereda los comportamientos de uno normal, solo que tiene algunas características adicionales en su forma de iluminarse. Si deseas establecer la posición inicial del Spinner usa el método Debido a que los elementos de nuestro Spinner son estáticos, podemos utilizar un atributo muy útil del elemento <Spinner> llamado android:entries. En él puedes asignar el arreglo de strings que se ha predefinido en los recursos y automáticamente se inflarán implícitamente los elementos el Spinner sin necesidad de un adaptador. Solo basta con que te dirijas a la pestaña de diseño y modifiques el atributo con el siguiente valor «@array/Games»: En esta sección verás cómo definir el flujo de tu aplicación de acuerdo a la opción del Spinner que sea seleccionada por el usuario. Para cumplir ese objetivo se empleará la escucha OnSelectedItemListener. El primer paso que debes realizar es implementar sobre tu actividad Main la escucha. Luego en el método onCreate() le asignas la referencia de la escucha a tu Spinner: A continuación se deben implementar los métodos onItemSelected() y onNothingSelected() relacionados con OnItemSelectedListener. El primero es llamado cuando has seleccionado un elemento del Spinner y el segundo cuando no tiene ítems seleccionados. ¿Cuál es la idea?, vas a sobrescribir onItemSelected() y añadirás las instrucciones necesarias para que visualice un Toast. Este debe mostrar el nombre del elemento que se ha seleccionado. Veamos: La primera tarea que se ha realizado es guardar la posición y el valor del texto en dos variables privadas llamadas position y selection (Si revisas el proyecto, han sido declaradas en el inicio de Main). El parámetro position del método onItemSelected() se refiere al índice del elemento seleccionado, por lo que te queda fácil obtener una instancia con getItemAtPosition(). Luego puedes consultar el valor de su atributo text con toString(). Finalmente creas un Toast prefabricado y muestras el contenido de la selección. En esta sección aprenderás a crear los ítems de un Spinner desde un cursor que contenga el resultado de una consulta en SQLite (¿Aún no sabes cómo diseñar una base de datos? Obtén mi ebook con 8 pasos para crear modelos de datos El proceso es el mismo que con las listas. Como ya sabes, solo debes usar la clase SimpleCursorAdapter y todo estará resuelto. Suena fácil pero es mejor aclararlo con un ejemplo. La siguiente aplicación que estudiarás a continuación se llama Lyrik. Esta aplicación muestra las letras de las canciones de un artista disponible, clasificados por género musical. [sociallocker id=»7121″][/sociallocker] Su diseño se basa en una actividad principal (Main) con dos spinners ( Por lo que el spinner de los artistas depende del género, representando una cardinalidad uno a muchos (1:N) en la base de datos. Una vez definido todas las características de la base de datos se crea un script que automatice su creación en cualquier momento. El nombre de clase es DataBaseScript (Obsérvala en el proyecto). Si detallas la clase Main del proyecto, verás que el primer paso fue inicializar un adaptador del tipo SimpleCursorAdapter de la siguiente forma: El constructor ha recibido el layout prefabricado simple_spinner_item.xml y como fuente de datos se ha establecido el cursor que retorna el método getAllGenres() de la clase LyrikDataSource. Como su nombre lo indica, este método obtiene todas las filas de la tabla Genres de la base de datos. Su definición no es más que una sentencia SELECT que selecciona todas las columnas y registros: Hasta aquí todo muy bien. Ahora veamos la estrategia para poblar el Spinner de artistas. ¿Cómo hacer para que el segundo Spinner sea poblado al elegirse un ítem del primer Spinner? La respuesta está en los eventos de selección. Bien sabemos que la interfaz OnItemSelectedListener es quién implementa el método onItemSelected() para la ejecución de actividades de selección, así que es allí donde debemos aplicar nuestra lógica. Ahora pregúntate: ¿Como obtengo los nombres de los artistas que están relacionados con un género? En esencia, esta es una duda que debemos resolver primero desde el modelo de datos, es decir, en la base de datos. Y la solución es sencilla, simplemente debes consultar aquellos artistas cuyo valor de su campo idGenre sea igual al asignado en tiempo real por el flujo del programa. Por ejemplo… La siguiente consulta obtiene el id y nombre de los artistas del genero con código 1001: Así que generalizaremos esta sentencia con el siguiente método llamado getArtistsByGenre() – «Obtener artistas por genero»: Si detallas bien, usamos el placeholder ‘?’ para reemplazarlo por genreSelection, que es el parámetro de entrada que representa el id del género requerido. Esto facilita tu trabajo para crear el adaptador del Spinner de artistas. Finalmente implementamos el método onItemSelected(), donde obtienes el id del género seleccionado y luego inicias el adaptador: Recuerda que si el adaptador maneja cursores el resultados obtenido por getItemAtPosition() debe ser casteado a Cursor. Seguidamente puedes obtener el valor de la fila deseado. Para activar el Spinner de artistas de forma personalizada se creó el método activeArtistSpinner(). Su trabajo es crear el adaptador con el id de entrada a través del método getArtistsByGenre(). Luego lo asignas al Spinner y finalmente relacionas la escucha: Ahora solo queda agregar al método onItemSelected() las acciones que se realizaran cuando sea seleccionado un elemento del ArtistSpinner: La idea fue asignarle al TextView un mensaje del tipo «Letras de <Artista>», donde <Artista> será reemplazado por la selección actual del usuario. Si logras recordar un poco sobre la anatomía de la Action Bar tendrás presente que existe una sección llamada View Control. Se había dicho que en este espacio era posible inflar Views que interactuan con el contenido actual de la actividad o fragmento. Muy bien, en este apartado veremos cómo inflar un Spinner justo en ese lugar para implementar un filtro de datos. Dicho filtro será incluido en una aplicación llamada Fazhion. Esta aplicación fue pedida por un cliente imaginario que desea proyectar en una actividad todos los estilos de zapatos que vende su negocio. Quiere que se muestre el nombre del zapato, su precio, la descripción y una miniatura de su aspecto. Además desea que el cliente pueda filtrar los productos por precio y nombre. Así que lo que harás es añadir un Spinner a la Action Bar para solucionar este problema. Donde el resultado final será algo como esto: [sociallocker id=»7121″][/sociallocker] La aplicación Fazhion se compone de una ListActivity principal llamada Main. Los elementos de la lista son inflados a través de un CursorAdapter personalizado que accede a la base de datos Fazhion previamente creada. Ahora veamos como se hizo para añadir el Spinner a la Action Bar: Puedes usar un ArrayAdapter personalizado o un SimpleCursorAdapter como vimos con anterioridad. También puedes personalizar los ítems al igual que en la captura de la aplicación Fazhion, cuyo Spinner tiene un ImageView en cada ítem. Si deseas añadir un ImageView al Spinner debes crear un layout personalizado. Fazhion utiliza el layout spinner_item.xml, cuyo diseño se proyecta en el siguiente bosquejo: Luego debes crear un adaptador personalizado que soporte el anterior layout. Esto implica crear un origen de datos basado en una clase que represente cada elemento del Spinner. Si abstraes las características de cada elemento del Spinner, deducirás que poseen un icono que proyecta su significado y un nombre. Así que crea una nueva clase con estas características y llámala SpinnerItem: Por último debes crear un adaptador que infle cada view a través de una lista de tipo SpinnerItem. A este le denominarás FilterAdapter: Si eres buen observador verás algo inusual que nunca antes habíamos hecho al definir un adaptador. Hemos sobrescrito un método llamado getDropDownnView(). Este método tiene las mismas funciones de getView(), solo que se ejecuta cuando el Spinner es desplegado. Como ya habías visto, los ítems del Spinner pueden utilizar otro diseño en ese momento. Debido a que no deseamos que tomen otra forma, simplemente retornamos en el mismo view que genera el método getView(). Ahora solo crea en tu actividad principal una lista de ítems para el Spinner y luego asignalos en el constructor del nuevo FilterAdapter: Para los eventos usaremos las clases OnNavigationListener. Esta interfaz te permite decidir qué sucederá cuando el usuario selecciona uno de los elementos del Spinner en la Action Bar. En el caso de Fazhion se ejecutaron las consultas necesarias en la base de datos para obtener los registros de forma ordenada: La escucha OnNavigationLisner te entrega a disposición el método onNavigationItemSelected() para definir las acciones de cada elemento del Spinner. Donde el parámetro entero position será nuestro referente para identificar cada elemento. Al seleccionar el ítem en la posición 0, el adaptador de la lista actualiza el cursor a través de un método llamado getShoesOrderPrice(). Este ordena por precio la lista de los zapatos. Será en orden descendente si recibe false y en orden ascendente si recibe true. En este caso consulta los registros de forma descendente y para la posición 1 se hace en forma ascendente. El elemento en la posición 2 llama a getShoesOrderAZ(). Este método ordena alfabéticamente los nombres de los zapatos. Si recibe true lo hace de A-Z, si es false ordena de Z-A. Revisa la clase FazhionDataSource del proyecto. Allí encontrarás la definición de ambos métodos. La navegación de lista le indica a la action bar que inflaremos un menú con una lista de ítems, para gestionar la navegación entre fragmentos o actividades. Pero en el caso de Fazhion no habrá navegación, si no que filtraremos los elementos de la ListActivity. Si deseas activar este modo y relacionar la escucha haz lo siguiente: El código anterior muestra como se activa el modo de navegación por lista con el método setNavigationMode(). Este recibe como parámetro una constante llamada NAVIGATION_MODE_LIST para que el Spinner sea funcional sobre la Action Bar. Luego debes asignar la escucha y el adaptador del Spinner con el método setListNavigationCallbacks(). Con ello tendrás habilitado el modo de navegación y la funcionalidad DropDown del Spinner. Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!Poblar un Spinner con un Adaptador
//ArrayAdapter para conectar el Spinner a nuestros recursos strings.xml
protected ArrayAdapter<CharSequence> adapter;
...
//Obtener instancia del GameSpinner
Spinner spinner = (Spinner) findViewById(R.id.GameSpinner);
//Asignas el origen de datos desde los recursos
adapter = ArrayAdapter.createFromResource(this, R.array.Games,
android.R.layout.simple_spinner_item);
//Asignas el layout a inflar para cada elemento
//al momento de desplegar la lista
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
//Seteas el adaptador
spinner.setAdapter(adapter);
setDropDownViewResource()
. Su objetivo es setear un layout para cada ítem del Spinner, para cuando se despliegue la lista.simple_spinner_dropdown_item.xml
, el cual representa un text view seleccionable o CheckedTextView
.setSelection()
con un índice entero como parámetro. Si pones atención, en Gamerty creamos una constante llamada DEFAULT_POSITION
a la cual se le asigno el entero 3 que corresponde a "Halo 4"
.Poblar un Spinner con el Atributo entries
O ir a la definición XML y realizar la respectiva asignación:<Spinner
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/GameSpinner"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
<strong>android:entries="@array/Games" </strong>/>
Manejo de eventos de un Spinner
public class Main extends Activity implements OnItemSelectedListener {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//Asignado la escucha
spinner.setOnItemSelectedListener(this);
...
}
...
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//Salvar la posición y valor del item actual
this.position = position;
selection = parent.getItemAtPosition(position).toString();
//Mostramos la selección actual del Spinner
Toast.makeText(this,"Selección actual: "+selection,Toast.LENGTH_SHORT).show();
}
Poblar un Spinner desde una Base de Datos SQLite
Al igual que el primer ejemplo, usa la siguiente caja de descarga:GenreSpinner
y ArtistSpinner
). El primero para desplegar los géneros disponibles y el segundo para proyectar los artistas que pertenecen a ese género.
Teniendo en cuenta lo anterior, la base de datos tendría dos tablas: Géneros(Genres) y Artistas(Artists). Donde Artistas recibiría una llave foránea relacionada con la llave principal de Géneros.Usar la clase SimpleCursorAdapter con un Spinner
//Creando Adaptador para GenreSpinner
genreSpinnerAdapter = new SimpleCursorAdapter(this,
android.R.layout.simple_spinner_item,
dataSource.getAllGenres(),
new String[]{DataBaseScript.GenreColumns.NAME_GENRE},
new int[]{android.R.id.text1},
SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
public Cursor getAllGenres(){
//Seleccionamos todas las filas de la tabla Genres
return database.rawQuery(
"select * from " + DataBaseScript.GENRES_TABLE_NAME, null);
}
Crear una dependencia entre dos Spinners
SELECT _id,name
FROM Artists
WHERE idGenre = 1001;
public Cursor getArtistsByGenre(String genreSelection) {
//Argumentos del WHERE
String selectionArgs[] = new String[]{genreSelection};
String query =
"select "+DataBaseScript.ArtistColumns.ID_ARTIST+","+DataBaseScript.ArtistColumns.NAME_ARTIST+
" from "+DataBaseScript.ARTISTS_TABLE_NAME +
" where "+DataBaseScript.ArtistColumns.ID_GENRE+
"= ?";
return database.rawQuery(query,selectionArgs);
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//Obteniendo el id del Spinner que recibió el evento
int idSpinner = parent.getId();
switch(idSpinner) {
case R.id.GenreSpinner:
//Obteniendo el id del género seleccionado
Cursor c1 = (Cursor) parent.getItemAtPosition(position);
String genreSelection = c1.getString(
c1.getColumnIndex(DataBaseScript.GenreColumns.ID_GENRE));
//Poblar el ArtistSpinner
activeArtistSpinner(genreSelection);
break;
...
private void activeArtistSpinner(String genreSelection) {
//Creando Adaptador para ArtistSpinner con el id del género
artistSpinnerAdapter = new SimpleCursorAdapter(this,
android.R.layout.simple_spinner_item,
dataSource.getArtistsByGenre(genreSelection),
new String[]{DataBaseScript.ArtistColumns.NAME_ARTIST},
new int[]{android.R.id.text1},
SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
//Seteando el adaptador creado
artistSpinner.setAdapter(artistSpinnerAdapter);
//Relacionado la escucha
artistSpinner.setOnItemSelectedListener(this);
}
case R.id.ArtistSpinner:
//Obteniendo el nombre del artista seleccionado
Cursor c2 = (Cursor) parent.getItemAtPosition(position);
String artistSelection = c2.getString(
c2.getColumnIndex(DataBaseScript.ArtistColumns.NAME_ARTIST));
//Cambiando el texto de LyricList según el Artista seleccionado
lyricList.setText(getResources().getString(R.string.LyricList)+" "+artistSelection);
break;
Añadir un Spinner a la Action Bar
De nuevo, pon tu correo para desbloquear el link de descarga de Fazhion:Paso 1: Crear el Adaptador del Spinner
Y su descripción xml es la siguiente:<?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="match_parent"
android:padding="5dp">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:id="@+id/ItemIcon"
android:src="@drawable/ic_launcher"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"
android:layout_marginRight="3dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Texto"
android:id="@+id/TextItem"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/ItemIcon" />
</RelativeLayout>
public class SpinnerItem {
private String Name;
private int iconId;
public SpinnerItem(String name, int iconId) {
Name = name;
this.iconId = iconId;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public int getIconId() {
return iconId;
}
public void setIconId(int iconId) {
this.iconId = iconId;
}
}
public class FilterAdapter extends ArrayAdapter {
public FilterAdapter(Context context, List<SpinnerItem> objects) {
super(context, 0, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
/*
Obtener el objeto procesado actualmente
*/
SpinnerItem currentItem = getItem(position);
/*
Obtener LayoutInflater de la actividad
*/
LayoutInflater inflater = (LayoutInflater) parent.getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
/*
Evitar inflar de nuevo un elemento previamente inflado
*/
if(convertView==null){
convertView = inflater.inflate(R.layout.spinner_item, parent, false);
}
/*
Instancias del Texto y el Icono
*/
TextView name = (TextView)convertView.findViewById(R.id.TextItem);
ImageView icon = (ImageView)convertView.findViewById(R.id.ItemIcon);
/*
Asignar valores
*/
name.setText(currentItem.getName());
icon.setImageResource(currentItem.getIconId());
return convertView;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
/*
Debido a que deseamos usar spinner_item.xml para inflar los
items del Spinner en ambos casos, entonces llamamos a getView()
*/
return getView(position,convertView,parent);
}
}
/*
Inicializar los items estáticamente
*/
List items = new ArrayList();
items.add(new SpinnerItem("Mayor Precio",R.drawable.ic_higher_prices));
items.add(new SpinnerItem("Menor Precio",R.drawable.ic_lower_prices));
items.add(new SpinnerItem("Alfabéticamente",R.drawable.ic_alphabetical_order));
//Creando el nuevo adaptador para el Spinner
spinnerAdapter = new FilterAdapter(this,items);
Paso 2: Implementar la interfaz OnNavigationListener
OnNavigationListener navigationListener =new OnNavigationListener() {
/*
0 = Mayor Precio
1 = Menor Precio
2 = Alfabeticamente
*/
@Override
public boolean onNavigationItemSelected(int position, long itemId) {
switch(position){
case 0:
/*
Obtener precios en orden descendente
*/
fazhionAdapter.changeCursor(dataSource.getShoesOrderPrice(false));
break;
case 1:
/*
Obtener precios en orden ascendente
*/
fazhionAdapter.changeCursor(dataSource.getShoesOrderPrice(true));
break;
case 2:
/*
Obtener nombres en orden alfabético
*/
fazhionAdapter.changeCursor(dataSource.getShoesOrderAZ(true));
break;
}
return true;
}
};
Paso 3: Habilitar el Modo de Navegación de Lista
//Obteniendo una instancia de la Action bar
ActionBar actionBar = getActionBar();
if( actionBar != null) {
//Habilitiando el modo de navegación con lista
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
//Seteando la escucha y el spinner relacionado
actionBar.setListNavigationCallbacks(spinnerAdapter, navigationListener);
}
Únete Al Discord De Develou