Programación Android, Cómo usar Fragments con ViewPager y efecto Swipe

Share if you like...Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0
Cómo usar Fragments con ViewPager y efecto Swipe
ActionBar con Pestañas

En esta ocasión vamos a crear un tutorial sobre cómo usar Fragments con ViewPager y efecto Swipe, es decir, mostraremos un conjunto de pestañas en la parte superior de la pantalla y cada pestaña tendrá asignado un Fragment para mostrar una determinada información en pantalla, y habilitaremos el efecto Swipe (desplazamiento lateral) para que se muestre de forma correcta la pestaña seleccionada en todo momento.

Como en los ejemplos que se pueden ver por internet sólo explican cómo crear pestañas que contengan pantallas en blanco, o con poca información, y si se utiliza el manager que nos ofrece el ADT para crear este tipo de controles nos crea un archivo que contiene todas las clases que se utilizan (lo que no permite organizar el código como más le guste a uno), en esta ocasión vamos a crear clases diferentes para los fragments que podamos utilizar, para el adaptador que se necesita para gestionar los Tabs (pestañas), etc. La compatibilidad que obtendremos usando las librerías nativas será a partir de API 11, que corresponde a la versión 3.0 de Android.

Crear la estructura de Tabs

En primer lugar vamos a crear un nuevo proyecto, y haremos dos cambios sobre el proyecto que se crea por defecto:

  • Cambiar el archivo XML del layout, para que contenga un ViewPager en lugar de un RelativeLayout, LinearLayout, etc. El contenido del xml sería el siguiente:
<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" />
  • Modificar el archivo Java de la Activity principal, para que extienda de FragmentActivity e implemente el TabListener del ActionBar:
public class MainActivity extends FragmentActivity implements
		ActionBar.TabListener {

Nota: Prestar especial atención a que el import que se haga de FragmentActivit sea import android.support.v4.app.FragmentActivity; para que exista compatibilidad con versiones anteriores a 4.0

Esto nos obligará a implementar los métodos del interface TabListener, que son los siguientes:

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
	// TODO Auto-generated method stub
		
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
	// TODO Auto-generated method stub
		
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
	// TODO Auto-generated method stub
		
}

Crear Layouts para Fragments

El siguiente paso será crear un Layout por cada Tab que contendrá nuestra aplicación. En este caso vamos a crear un único Layout, y le pondremos un TextView, pero en lugar de tener un texto predefinido, vamos a asignarle el texto en la propia creación del Fragment (se entenderá mejor cuando lleguemos a ese punto). La estructura del xml sería 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" >

    <TextView
        android:id="@+id/tvText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

Crear Fragments

Una vez que tenemos el Layout definido, el siguiente paso es crear una clase en Java que extienda de Fragment, que será la encargada de cargar el Layout que hemos creado para mostrarlo en pantalla. En este caso sólo necesitamos crear una clase, porque sólo tenemos un Layout, pero en el caso de que tuvieramos distintos layouts, con distintas estructuras, crearíamos una clase por cada uno de los layouts que definieramos, ya que cada clase es la encargada de gestionar el contenido de cada pestaña, y por lo tanto cada una cargaría un Layout diferente.
En este punto añadiremos información sobre la mayoría de tutoriales que existen, pues siempre suelen mostrar su utilización a través del constructor por defecto. Sin embargo, como nosotros queremos pasar información a este Fragment, tendremos que hacerlo al construirlo. Lo primero que podríamos pensar es crear un constructor, además del constructor por defecto, para pasar en el propio constructor la información que necesita el Fragment. Sin embargo esto no es posible, ya que no se pueden crear constructores que contengan parámetros en ningún Fragment. Al hacerlo el compilador nos daría un error y no permitiría compilar el proyecto.
El motivo por el que sucede esto es que para utilizar Fragments, la información que afecta al Fragment debe estar incluida en un Bundle, pero si creamos nuestro propio constructor podemos evitar el uso del Bundle, por lo tanto, para pasar esta información necesaria al Fragment debemos hacerlo de otra manera.
Ahora vamos a crear nuestra clase Fragment, y crearemos el método que creará la instancia del Fragment y almacenará el texto que queremos mostrar posteriormente. Este es el punto en que asignamos el texto al Fragment:

import android.support.v4.app.Fragment;

public class CustomFragment extends Fragment {
	private final static String KEY_REG_TEXT = "texto";

	public static CustomFragment newInstance(String text) {
		CustomFragment frag = new CustomFragment();
		
		Bundle args = frag.getArguments();
		if(args == null)
			args = new Bundle();
		
		args.putString(KEY_REG_TEXT, text);
		
		frag.setArguments(args);
		
		return frag;
	}
}

Y a continuación vamos a sobreescribir el método onCreateView, que es el encargado de cargar el xml del Layout, y mostrarlo en pantalla. En este método también recogeremos los datos que necesitamos y que previamente habíamos guardado al crear la instancia del Fragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
		Bundle savedInstanceState) {
	// Inflamos la Vista que se debe mostrar en pantalla.
	View rootView = inflater.inflate(R.layout.fragment_layout, container,
			false);
	// Creamos instancia del TextView.
	TextView tvText =  (TextView)rootView.findViewById(R.id.tvText);
	// Recogemos el texto que guardamos al crear el Fragment.
	String text = getArguments().getString(KEY_REG_TEXT);
	// Mostramos el texto en el TextView.
	tvText.setText(text);
		
	// Devolvemos la vista para que se muestre en pantalla.
	return rootView;
}

Y con esto ya tendremos nuestro Fragment listo para poder mostrarlo en pantalla.

Crear el Adapter para los Tabs

El siguiente paso será crear el Adaptador que va a manejar nuestras pestañas, que a su vez va a ser el que haga llamada a los distintos Fragments de cada pestaña. Nuestro adaptador va a extender de FragmentPagerAdapter, y va a crear 5 pestañas, mandando a cada una su Fragment y el texto que debe mostrar. En realidad en él hay que configurar poco, sólo hay que tocar dos métodos. El primero getItem que es el encargado de mostrar el Fragment asignado a cada pestaña, y el segundo getCount, que devolverá el número de pestañas que contiene el Adaptador:

public class TabsAdapter extends FragmentPagerAdapter {

	public TabsAdapter(FragmentManager fm) {
		super(fm);
	}

	@Override
	public Fragment getItem(int index) {
		if(index < 6) {
			switch(index) {
			case 0:
				return CustomFragment.newInstance("Texto de la pestaña nº 1.");
			case 1:
				return CustomFragment.newInstance("Texto de la pestaña nº 2.");
			case 2:
				return CustomFragment.newInstance("Texto de la pestaña nº 3.");
			case 3:
				return CustomFragment.newInstance("Texto de la pestaña nº 4.");
			case 4:
				return CustomFragment.newInstance("Texto de la pestaña nº 5.");
			}
		}
		return null;
	}

	@Override
	public int getCount() {
		return 5;
	}

}

Mostrar Fragments con TabsPager y efecto Swipe

Ahora que ya tenemos nuestras vistas, y el adaptador creado, vamos a volver a nuestro FragmentActivity, que es el que tiene que mostrar los Tabs y sus Fragments. Esta clase va a necesitar 3 campos, un objeto de la clase ViewPager, otro de nuestro Adapter para las pestañas y una instancia del ActionBar, que contendrá las pestañas. Estos 3 objetos se inicializarán en el método onCreate, y después se establecerá el adapter del ViewPager, y se establecerá el modo de navegación del ActionBar para que muestre las pestañas:

private ViewPager vPager;
private TabsAdapter tAdapter;
private ActionBar aBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
		
	vPager = (ViewPager)findViewById(R.id.pager);
	tAdapter = new TabsAdapter(getSupportFragmentManager());
	aBar = getActionBar();
		
	vPager.setAdapter(tAdapter);
	aBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}

Si vemos el constructor de nuestro adapter, necesita un FragmentManager, que lo hemos obtenido a través del método getSupportFragmentManager, que pertenece a la clase FragmentActivity.
A continuación vamos a crear un recurso XML, que va a contener un string-array con los títulos que mostrarán las pestañas:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="tabs">
        <item >Tab 1</item>
        <item >Tab 2</item>
        <item >Tab 3</item>
        <item >Tab 4</item>
        <item >Tab 5</item>
    </string-array>
</resources>

Y ahora añadiremos todas las pestañas, con su título correspondiente, al ActionBar, escribiendo el siguiente código en el método onCreate, debajo de lo que ya teníamos escrito:

for (String title : getResources().getStringArray(R.array.tabs)) {
	aBar.addTab(aBar.newTab().setText(title).setTabListener(this));
}

El siguiente paso es habilitar el cambio de pestaña seleccionada al realizar el Swipe, añadiendo el siguiente código al final del método onCreate:

// Habilitar swipe entre tabs.
vPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

	@Override
	public void onPageSelected(int position) {
		aBar.setSelectedNavigationItem(position);
	}

	@Override
	public void onPageScrolled(int arg0, float arg1, int arg2) {
	}

	@Override
	public void onPageScrollStateChanged(int arg0) {
	}
});

Y por último tan sólo hay que hacer que se muestre el Fragment cuando se selecciona una pestaña, para eso nos vamos al método que hemos implementado, del interface TabListener, onTabSelected, y en él estableceremos qué elemento se debe mostrar:

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
	// Establecer el fragment que se debe mostrar.
	vPager.setCurrentItem(tab.getPosition());

}

El resultado de todo esto quedaría así:

Pantalla
Pantalla

Descargas

Puedes descargar el proyecto completo aquí.

Share if you like...Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0
The following two tabs change content below.
Reborn as IT Developer. Desarrollador Android y fundador de Proyecto Simio. "En realidad, yo no puedo enseñar nada a nadie, sólo puedo hacerles pensar." - Sócrates.

36 thoughts on “Programación Android, Cómo usar Fragments con ViewPager y efecto Swipe

  1. Mi proyecto es de encuestas y tengo EditText en cada pestaña,tengo 3 pestañas el problema es que cuando cambio de pestañas y regreso se borran los datos de los editText

  2. Excelente tutorial, calidad!

    Y ya que estoy me surge una pregunta, se podría añadir un progressbar? es decir, la idea es tener como un tutorial e ir avanzando haciendo swipe. No sé si hay más opciones… o también poder añadirle un botón de siguiente para saltar a la siguiente página…

    Muchas gracias por todo!

  3. Buenos días, me estoy obsesionando con algo que no puede ser complicado pero me estoy liando y no lo saco. El tema es el siguiente:
    Tengo una actividad que recibe una serie de variables (id, latitud, longitud), esta actividad utiliza dos tabs con FragmentPagerAdapter, el primero con su actividad que debería recibir el id para actualizar los datos de su fragment y la segunda actividad debería recibir la latitud y la longitud para actualizar un mapa. Lo que quiero saber es como pasar estas variables a cada actividad para actualizar sus respectivos fragments.
    Gracias de antemano por el tiempo y la atención prestada.

  4. Buenas noches y gracias por tu Blog.

    Mi pregunta es, si quisiéramos no ofrecer swipe y cambiar a una determinada Tabs por código, ¿Como lo haríamos?

    Gracias

  5. Buenas tardes, amigo tu post me ha ayudado demasiado pero hasta ahora no consigo una manera de trabajar un listview con un json & un fragment, me podrías ayudar con ello?

    1. Buenas Miguel.

      El proceso sería exactamente igual que en una Activity, la única diferencia sería que en lugar de realizar los procesos de inicialización de variables en el método onCreate de la Activity lo harías en el método onCreateView del Fragment. Después la gestión del json, adapter, etc. sería exactamente igual que en la Activity.

      Un saludo.

  6. Excelente ejemplo!!
    Me gustaria saber si es posble evitar la recargar de un un map fragment cada vez que cambio la pestanna del tab. Muchas gracias.

    1. Buenas Dario,

      Puedes hacerlo de dos formas, en función de lo que puedas necesitar. Una de ellas es utilizando el patrón Singleton, en el que dejas que el propio Fragment guarde su instancia y configuración, de esta manera sólo tendrías que hacer Fragment.getInstance() para obtener la instancia.
      La segunda opción es que en el TabsAdapter tengas como variables globales los fragments que necesites, que en el constructor se inicialicen referencias al fragment y posteriormente en el switch en el que decides qué fragment mostrar no inicialices una nueva instancia, sino que directamente devuelvas la que ya tenías previamente inicializada.

      Muchas gracias por tu comentario :)
      Un saludo.

    1. Si descargas el ejemplo podrás ver que funciona perfectamente. El problema puede ser que estés aplicando a tu proyecto un tema que no sea compatible con estas pestañas.

      Prueba a cambiar el tema (intenta poniendo el mismo tema que utilicé yo al crear el ejemplo) de la aplicación a ver si con eso lo consigues solucionar.

      Un saludo.

  7. Excelente ejemplo solo quisiera saber como hacer para que no se cargue el siguiente Tab?? he notado que al inicio se muestra el Tab 0 (cero) pero se carga en background el Tab 1 como evito esto??

  8. Buenas he seguido tu ejemplo, y está genial. Pero me surge un problema , mi intención es poner un Mapa en una de las TAB y me da un error que no se como solucionar, me dice que quiero poner un fragment dentro de otro fragment, por primera vez lo carga todo bien xo si cambias de pestanyas da error.
    PD: he buscado por stackoverflow, etc y nada, … Muchas gracias por tu atención. A parte estoy usando la librería Sherlock para poder trabajar en versiones anteriores a 3.0.

    Saludos.

    1. Buenas Mikel, para poder ayudarte tendría que ver el proyecto para ver qué estás haciendo y donde podría estar el problema. Sin más información que esta no puedo saber donde está el fallo.

      Un saludo.

  9. Muy bueno, aprendi muchas cosas.. una pregunta muy basica, como hago si a una pestaña le quiero agregar un sprinner x ejemplo? o a cada pestaña agregarle un estructura de controles diferentes… muchas gracias :)

    1. Ten en cuenta que esto es un simple ejemplo de cómo puede usarse, pero en cada pestaña puedes tener una instancia de una clase distinta, de manera que puedes tener creadas 5 clases que extiendan de Fragment y en cada posición usas una de esas clases.

      De esta forma podrás tener cada pestaña con información totalmente distinta sin ningún tipo de problema.

      Un saludo.

  10. He logrado acceder a las variables de los Fragments desde el ultimo Fragment de esta forma:
    List lista= getFragmentManager().getFragments();
    CustomFragmentEnfRespCuest f=(CustomFragmentEnfRespCuest)lista.get(0); para el primero y asi con todos por orden.

    EL problema ahora es cuando me salto algunos fragments y voy directo al ultimo mediante el action bar para salvar los datos encuentro que los fragments
    que me he saltado sin visitarlos haciendo swip no estan en la lista que me da el FragmentManager y pos supuesto me
    da error al tratar de obetenerlos de la lista. La unica solucion que se me ocurre es hacer swip automaticamente cuando se cargue el primer Fragment pero
    he probad de todo y nada me funciona. Por favor si pudieras arrojar un poco de luz sobre esto te lo agradeceria mucho.

    Tambien he probado los metodos put y saveinstance del fragment manager sin resultado alguno ,ademas probe haciendo que el
    vPager cambie de pagina con un ciclo for en el On create de la actividad contenedora y nada cuando carga la actividad aparece ubicado en el ultimo Fragment
    pero no los ha puesto es el manager .solo ha cargado los ultimos 4 fragments en las posiciones de 0 a 3 de la lista de fragments y el ciclo va desde el primero al ultimo.
    for(int i=0;i<11;i++)
    {
    vPager.setCurrentItem(i);
    }
    La verdad es que no se que hacer ya.

    Saludos

    1. Cuando vayas a recoger toda la información, antes de recogerla del fragment comprueba si el objeto es null, y de ser así ya sabes que el usuario no ha podido rellenar la información necesaria en ese fragment.

      Un saludo.

  11. Hola, antes de todo felicitarte por tan buen tutorial muy bien explicado y facil de entender como hay pocos por ahi. Implemente tu ejemplo normal pero sin pasarle ningun control por parametro mis vistas estan creadas con los controles y todo y se las paso a la clase tabadapter. Hasta ahi todo bien . Mi aplicacion es sobre cuestionarios y hay que ir marcando en cada pagina o llenando campos de texto y quiero cuando este en la ultima pagina que el usuario tenga la posibilidad de guardarlo todo en una BD. MI pregunta es como podria acceder a la informacion que tiene un control de cualquier pagia desde la ultima pagina de mi viewpager. Saludos

    1. No sé exactamente cómo habrás estructurado el proyecto, pero si utilizas el fragment como variable de clase podrás acceder a todos sus datos, aunque no esté visible en pantalla ya estaría inicializado y sólo tendrías que sacar los datos que necesites con algún getter.

      Un saludo.

  12. Antes que nada felicitarte y agradecerte por tan buenos tutoriales de los cuales e aprendido mucho. Con respecto al ActionBart con pestañas, tengo un proyecto personal que usando el antiguo y feo tabhost carga en pestañas la información de listviews con adapter personalizados y datos que recoge de una base de datos sqlite y los coloca tanto en los listviews como en diferentes tablelayout y me funciona perfectamente (basándome en tus ejemplos de sqlite con listview y MVC, lo cual fue un gran tutorial con el que aprendi muchisimo) pero mi problema ahora es que quiero dejar de hacer uso del tabhost y usar el ActionBar con pestañas y reutilizar mi mismo código y no logro entender como es que lo hago, e probado varias cosas y de veras que me esta matando esto..

    de antemano gracias.

    Saludos.

  13. super bueno el post pero quede con una duda…cuando hago el cambio de pestaña deslizando el dedo funciona perfecto, pero si presiono el nombre de la pestaña no cambia de vista, se queda igual y la pestaña cambia (no se si me explico). que pasa en ese caso?

    otra cosa que quiero saber es como puedo pasarle una lista con adapter personalizado como la que hiciste en un post anterior (http://www.proyectosimio.com/programacion-android-listview-adapter-personalizado-i/) a las pestañas estas, llevo intentando unos 4 dias y no me funca :(

    repito super buen post sigue asi

    1. En cuanto al problema del cambio de pestañas, has comprobado que hayas implementado bien el listener a cada pestaña (tab)? Fíjate en la parte del código siguiente:

      for (String title : getResources().getStringArray(R.array.tabs)) {
      	aBar.addTab(aBar.newTab().setText(title).setTabListener(this));
      }
      

      Ese setTabListener es el que hace que cuando se pulse sobre una de las pestañas se seleccione el item que se debe mostrar, al implementar este método:

      @Override
      public void onTabSelected(Tab tab, FragmentTransaction ft) {
          // Establecer el fragment que se debe mostrar.
          vPager.setCurrentItem(tab.getPosition());
       
      }
      

      Con respecto al adapter personalizado, si lo que quieres es cambiar la apariencia de la pestaña, el funcionamiento no es igual que el del adapter del listado. En este caso el adapter se encarga de indicar qué fragment se debe mostrar. Para cambiar la apariencia de la pestaña lo haces al crearla, en la misma línea de código en la que he añadido la pestaña al actionbar (el bucle for que te he copiado antes) puedes hacer uso de métodos como setIcon() o setCustomView() para añadir un icono a la pestaña o establecer una vista personalizada.

      Si lo que realmente querías es que cada pestaña muestre un fragment distinto, en el adapter en lugar de hacer las llamadas que yo hago siempre al mismo fragment, creas los fragments que necesites y los muestras en su debida posición. Esto es en el switch del getItem() del adapter.

      Un saludo.

      1. sabes que tengo un problema con el ActionBar, cuando lo importo me lanza error en el nombre de la clase y me obliga a implementar nuevamente el metodo onSelect y ese es el problema que no deja hacer el cambio de pagina al presionar la pestaña creo ¿que crees que sea?

Deja un comentario