¿Te has preguntado como implementar pestañas en la Action Bar de tu aplicación Android, pero aún no encuentras el tutorial indicado?, si la respuesta es afirmativa, entonces te aseguro que este artículo te será de gran ayuda. Quédate y podrás aclarar el uso de Tabs y la navegación horizontal en tus aplicaciones Android.
Además terminarás creando una aplicación con el siguiente aspecto:
(Link de descarga al final del tutorial)
Conocimientos que aprenderás:
CONTENIDO
- Patrón de paginado horizontal en Android
- Ejemplo de una Aplicación Android para un Restaurante
- Usar un ViewPager con fragmentos
- Añadir elementos a un ViewPager con un FragmentPagerAdapter
- Definición del Fragmento para el Adaptador
- Añadir tabs a la Action Bar
- Coordinar los elementos del ViewPager con las Tabs
- Bonus: Implementación de un Title Strip en Android
- Consideraciones Finales
Patrón de paginado horizontal en Android
El API de Android provee a los programadores muchas formas de navegación para implementar en sus aplicaciones. Una de ellas es el Paginado Horizontal o también llamado Swipe Views Pattern. Con este diseño el usuario cambia entre secciones de la aplicación con un gesto de arrastre horizontal en su dispositivo móvil. Veamos un ejemplo:
Además de la elegancia visual que proyectan los Swipe Views, también permiten una accesibilidad intuitiva y cómoda por parte del usuario al contenido principal de tu aplicación, mejorando la experiencia de su estadía.
Ejemplo de una Aplicación Android para un Restaurante
Como se vio al principio de este articulo, se ha creado una aplicación con un secciones de pedidos en un Restaurante llamada "Foodsty"
. Esta nos servirá de respaldo para aclarar el uso de Tabs en la Action Bar (Lee también Tutorial de la Action Bar en Android).
Lógicamente Foodsty consta de una actividad principal llamada Main
junto a un archivo de diseño activity_main.xml
. Adicionalmente se ha creado un archivo de diseño para los fragmentos que se usarán como items en el ViewPager
, cuyo nombre es fragment_content.xml
.
El objetivo de esta aplicación es mostrar una lista de cada uno de los productos disponibles por la tienda hipotética Foodsty, dependiendo de las tres categorías disponibles: "Platillos", "Postres" y "Bebidas".
El usuario podrá cambiar entres pestañas pulsando cada pestaña en la action bar (gesto tap) o arrastrando horizontalmente el contenido de las categorías (gesto swipe). Con estas características claras comencemos a crear nuestra aplicación Foodsty.
Usar un ViewPager con Fragmentos
Un ViewPager
es un widget que permite mostrar contenido en páginas o secciones individuales. Para intercambiar entre elementos se usa el gesto swipe de izquierda a derecha o viceversa. Al implementarlo es necesario crear un adaptador del tipo PagerAdapter
, que infle cada página de forma individual. Este proceso es muy parecido a cuando usábamos listas (Lee también Listas y Adaptadores en Android).
Dicho elemento pertenece a la Librería de soporte v4, así que no olvides incluir la dependencia en Gradle.
Debido a que la actividad principal de Foodsty está basada en un ViewPager
, se debe implementar un nodo raíz del tipo <ViewPager>
.
Veamos:
// Archivo activity_main.xml
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Añadir elementos a un ViewPager con un FragmentPagerAdapter
Lo siguiente es poblar nuestro ViewPager
a través de un adaptador del tipo FragmentPagerAdapter
. Esta subclase es una implementación especial para utilizar fragmentos en las páginas. ¿Por qué fragmentos?, porque así podrás tener el control del ciclo de vida del contenido, lo que te permite mayor flexibilidad en las operaciones que desees realizar.
Usa FragmentPagerAdapter
, si con antelación conoces la cantidad de fragmentos que tendrá tu ViewPager
. De lo contrario usa el adaptador FragmentStatePagerAdapter
para indicar que la cantidad será variable. Esta clase te ayudará a liberar memoria de aquellos fragmentos que no tienen el foco.
Crear nuestro propio adaptador de fragmentos es muy simple. Solo debes tener claro los siguientes elementos básicos a sobrescribir:
- Constructor: Es necesario crear un constructor que reciba el
FragmentManager
(Lee también Fragmentos en una aplicación Android) asociado al contexto donde se ejecuta elViewPager
. getCount()
: Sobrescribe este método para que retorne en la cantidad de elementos que tendrá tu pager.getPageTitle()
: Este método permite obtener el título de cada pestaña. Condiciona con unswitch
la posición de cada fragmento para asignar el nombre correcto.getItem()
: Este es el método que fabrica cada uno de los fragmentos de acuerdo a las características que hayas declarado.
Aclaradas estas especificaciones podemos ver la definición del adaptador FoodPagerAdapter
:
/*
Adaptador de fragmentos para el ViewPager
*/
public static class FoodPagerAdapter extends FragmentPagerAdapter {
public FoodPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return 3;
}
@Override
public CharSequence getPageTitle(int position) {
switch(position){
case 0: return "PLATILLOS";
case 1: return "POSTRES";
case 2: return "BEBIDAS";
default: return "";
}
}
@Override
public Fragment getItem(int i) {
// Crear un FoodFragment con el nombre como argumento
Fragment fragment = new FoodFragment();
Bundle args = new Bundle();
args.putString(FoodFragment.ARG_SECTION_NAME, getPageTitle(i).toString());
args.putInt(FoodFragment.ARG_SECTION_IMAGE, i);
fragment.setArguments(args);
return fragment;
}
}
Si prestas atención, FoodPagerAdapter
es una clase estática anidada debido a que solo la usaremos en este contexto. Otra propiedad a destacar es que su método getItem()
genera fragmentos del tipo FoodFragment
, clase que veremos enseguida.
Adicionalmente envía como argumento el titulo del item y el id de la imagen de presentación que se usará. Por lo que se usan las constantes ARG_SECTION_NAME
y ARG_SECTION_IMAGE
.
Definición del Fragmento para el Adaptador
El contenido de cada página es un simple TextView informativo que muestra la pestaña actual y una imagen alusiva al contenido. El texto mostrado representa el tipo de elementos que alberga el fragmento, algo como "Lista de <TipoElementos>". Así que cada vez que el usuario cambie las pestañas, la cadena se irá actualizando en tiempo real para reflejar el estado.
Esclarecida esta idea, creamos un layout sencillo para ubicar estos dos elementos:
// Archivo fragment_content.xml
<?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">
<ImageView
android:layout_width="match_parent"
android:layout_height="240dp"
android:id="@+id/imageView"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:scaleType="fitXY" />
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="24sp"
android:padding="32dp"
android:layout_below="@+id/imageView"
android:textColor="#c23"
android:textStyle="bold" />
</RelativeLayout>
Ahora se declara la clase FoodFragment
para representar el contenido:
/*
Fragmento que usaremos para cada pestaña
*/
public static class FoodFragment extends Fragment {
public static final String ARG_SECTION_NAME = "section_name";
public static final String ARG_SECTION_IMAGE = "section_image";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_content, container, false);
Bundle args = getArguments();
// Setear la imagen al fragmento
ImageView image = (ImageView)rootView.
findViewById(R.id.imageView);
image.setImageResource(imgIds[args.getInt(ARG_SECTION_IMAGE)]);
// Setear el texto
((TextView) rootView.findViewById(android.R.id.text1)).setText(
getString(R.string.section_title)+" "+args.getString(ARG_SECTION_NAME));
return rootView;
}
}
FoodFragment
también es una clase estática interna. Aunque para este ejemplo solo se declaró esta clase, ya que se usará la misma estructura en las 3 pestañas, no significa que siempre debas proceder de esta forma. Crea diferentes clases para cada fragmento si estos son completamente distintos y maneja la situación con un switch
.
Puedes crear un array global de tipo Fragment
dentro del adaptador e inicializarlo en su constructor con los fragmentos de distinta clase. Con esa definición estas habilitado para usar los indices del array en el método getItem()
en vez de preocuparte por sus tipos.
Añadir Tabs a la Action Bar
Lo primero que se debe hacer para crear pestañas dentro de la Action Bar es activar su modo de navegación por tabs. Para ello, obtén una instancia de la Action Bar y luego usa el método setNavigationMode()
. Este recibe como parámetro la constante del modo de navegación, que en este caso será NAVIGATION_MODE_TABS
:
// Obtener instancia de la Action Bar
final ActionBar actionBar = getActionBar();
// Activar el modo de navegación con tabs en la Action Bar
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
La lectura de eventos de las pestañas se realiza con la interfaz TabListener
de la clase ActionBar
. A cada pestaña se le debe asignar la escucha para que pueda responder a las instrucciones establecidas. Así que relacionaremos nuestra actividad principal con TabListener
y sobrescribiremos los métodos de esta escucha:
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
...
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
// Nada por hacer
}
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
// Coordinar la pestaña seleccionada con el item del viewpager
viewpager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
// Nada por hacer
}
}
El método onTabSelected()
se ejecuta cuando el usuario selecciona una pestaña, justo en ese momento actualizamos el contenido del ViewPager
con setCurrentItem()
, que recibe la posición del item a establecer en el foco. Esta posición se obtiene con getPosition()
de la tab actualmente procesada.
onTabUnselected()
se ejecuta cuando la pestaña sale del estado de selección y onTabReselected()
es iniciado cuando el usuario selecciona una pestaña que actualmente se encuentra seleccionada.
Por ultimo se crean 3 tabs con el método newTab()
y se añaden con addTab()
:
// Añadir 3 pestañas y asignarles un título y escucha
for (int i = 0; i < adapter.getCount(); i++) {
actionBar.addTab(
actionBar.newTab()
.setText(adapter.getPageTitle(i))
.setTabListener(this));
}
Usa setText()
para añadir el titulo de cada una con respecto al titulo del elemento correspondiente del adaptador y relaciona la escucha implementada con setTabListener()
.
Coordinar los elementos del ViewPager con las Tabs
Hasta ahora el usuario puede hacer tap en las pestañas y ver el contenido, pero aún no puede usar el gesto swipe. Por eso implementaremos la interfaz OnPageChangeListener
sobre nuestro pager. Con ella podemos sincronizar el cambio de pestañas cuando se cambie las páginas, es decir, el efecto contrario a cuando implementamos TabListener
.
viewpager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
// Coordinar el item del pager con la pestaña
actionBar.setSelectedNavigationItem(position);
}
});
Usa SimpleOnPageChangeListener
si deseas implementar solo los métodos necesarios de OnPageChangeListener
.
Como ves, se usa el método setOnPageChangeListener()
para añadir la escucha. El método onPageSelected()
se ejecuta cuando la página es seleccionada, por lo que se ordena en ese instante que la action bar cambie la pestaña a la posición determinada por el parámetro de entrada position
.
Finalmente junta todas las declaraciones y compone el método onCreate()
de la actividad principal:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Obtener instancia de la Action Bar
final ActionBar actionBar = getActionBar();
// Activar el modo de navegación con tabs en la Action Bar
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Deshabilitar el caret Up del icono de la aplicación
actionBar.setHomeButtonEnabled(false);
// Crear adaptador de fragmentos
adapter = new FoodPagerAdapter(getSupportFragmentManager());
// Obtener el ViewPager y setear el adaptador y la escucha
viewpager = (ViewPager) findViewById(R.id.pager);
viewpager.setAdapter(adapter);
viewpager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
// Coordinar el item del pager con la pestaña
actionBar.setSelectedNavigationItem(position);
}
});
// Añadir 3 pestañas y asignarles un título y escucha
for (int i = 0; i < adapter.getCount(); i++) {
actionBar.addTab(
actionBar.newTab()
.setText(adapter.getPageTitle(i))
.setTabListener(this));
}
}
Puedes descargar el código de todo el proyecto desde el siguiente enlace:
Descargar CódigoHasta este momento la aplicación se vería de esta forma:
Pero si tu eres mas fino y deseas usar iconos en cada pestaña, entonces buscas los drawables correspondientes y empleas el método setIcon()
para asignarlos.
//Las referencias de tus iconos
int idIcons[] = {R.drawable.icon1, R.drawable.icon2, R.drawable.icon3};
// Añadir 3 pestañas y asignarles un título y escucha
for (int i = 0; i < adapter.getCount(); i++) {
actionBar.addTab(
actionBar.newTab()
.setIcon(idIcons[i])
// Habilita el titulo si lo prefieres
// .setText(adapter.getPageTitle(i))
.setTabListener(this));
}
Con estos cambios la aplicación mutaría de esta forma:
Android Studio proporciona un método automático para generar una actividad con pestañas más el uso de Swipe Views con un ViewPager. Solo debes ubicarte en tu paquete java y dar click derecho, luego eliges New > Activity > Tabbed Activity.
Bonus: Implementación de un Title Strip en Android
Los Title Strips crea pestañas dinámicas en un ViewPager
. Se asemejan mucho a las pestañas de la action bar, solo que estos no tienen interacción con el usuario. Además un Title Strip muestra en tiempo real los items anteriores, el actual y los siguientes, a diferencia de las tabs que se mantienen fijas.
Su implementación es muy sencilla, solo anidas un elemento del tipo <android.support.v4.view.PagerTitleStrip>
dentro del elemento <ViewPager>
. Cada pestaña toma el nombre de la página correspondiente con el método getPageTitle()
, así que no debes preocuparte por implementarlo programáticamente.
Si deseas reemplazar las tabs por un PagerTitleStrip
entonces elimina todas las implementaciones de la action bar y agrega el elemento al archivo de diseño:
// Archivo activity_main.xml con PagerTitleStrip
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.PagerTitleStrip
android:id="@+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#c23"
android:textColor="#fff"
android:paddingTop="4dp"
android:paddingBottom="4dp" />
</android.support.v4.view.ViewPager>
Este diseño produciría el siguiente resultado:
Como alternativa, puedes ajustar el atributo layout_gravity
para que el Title Strip gravite en la parte inferior "bottom"
.
Un dato más. También es posible usar un Tab Strip con un indicador interactivo y visual, cuyo nombre es PagerTabStrip
. Este View utiliza una pequeña barra lateral que se desliza de un item a otro para representar la interacción con las páginas. Si quieres implementarlo solo cambia por el elemento <PagerTabStrip>
en tu archivo de diseño:
Consideraciones Finales
- En la nueva API 21 los modos de navegación de la action bar han quedado obsoletos, por lo cual el uso de pestañas en la Action Bar no son recomendables en el uso de aplicaciones para Android Lollipop, pero aún podemos usarlas en versiones anteriores. Recuerda que la propagación de un nuevo sistema operativo requiere cierto tiempo para dominar considerablemente una cuota de mercado. No obstante, el centro de desarrollo de Android recomienda el uso del componente
SlidingTabLayout
para la creación de pestañas. - También es importante saber que en el API 21 se ha creado un nuevo widget llamado
Toolbar
, el cual puede reemplazar la Action Bar. Este nuevo componente es muy flexible a la hora de modificar su aspecto y agregarle estilo (Hablaremos sobre él en futuros artículos). - Para la creación del estilo de Foodsty se usó la herramienta de código abierto Android Action Bar Style Generator de Jeff Gilfelt. Con ella puedes modificar el estilo de cada componente de la action bar, como el background, la transparencia, el tema padre, etc y producir todos los drawables necesarios para generar un tema global de la aplicación (Lee también Diseñar Temas y Estilos para tus Aplicaciones Android).
Iconos cortesía de IconFinder. Imágenes cortesía de Freepik.
James Revelo Urrea - Desarrollador independiente http://www.hermosaprogramacion.com