FragmentStatePagerAdapter con ListFragment en Android

Buenas tardes, en este tutorial vamos a ver 2 elementos. Uno de ellos ya lo hemos visto en entradas anteriores, los Fragment, y el otro es la primera vez que escribo sobre el, y es llamado ViewPager.

Un ViewPager es un elemento que nos permitirá deslizarnos hacia la derecha o a la izquierda en nuestra pantalla, mostrando diferente información. Se suelen usar normalmente con Fragment aunque también podemos usarlos sin ellos, usando layouts creados por nosotros mismos, pero en el caso de los Fragment, estos nos permiten controlar su ciclo de vida.


Vamos a ponernos manos a la obra:

  • Creación del Proyecto

    EL primer paso es crear un proyecto de aplicación de Android, o Android Application Project como veremos en el IDE. Para ello nos dirigimos a File->New->Android Application Project. En caso de que no veamos esta opción en la lista, nos dirigimos a “Other”, y en la ventana que nos aparezca lo buscamos, o si lo deseamos podemos filtrar la lista introduciendo “Android” en el campo de texto. Una vez que lo tengamos localizado, lo marcamos y pinchamos en aceptar.

    Nos aparecerá entonces el asistente de creación del proyecto. En la primera ventana nos va a aparecer la opción de asignar un nombre a nuestra aplicación y a nuestro proyecto. En mi caso no me he complicado y le he puesto de nombre “EjemploViewPager”, aunque es lo de menos y podeis darle el nombre que deseeis. El resto podemos dejarlo como está.

    A medida que avancemos por el asistente, nos encontraremos con la creación de nuestra primera Activity en la cual podremos darle nombre, al igual que su layout. En mi caso la he llamado “ViewPagerActivity”, y su layout se llama “activity_view_pager.xml”.

    Una vez tengamos nuestro asistente finalizado, podremos seguir con la construcción de nuestra aplicación.

  • FragmentA

    Para comenzar vamos a crear una nueva clase llamada “FragmentA” que hereda de la clase Fragment. Este Fragment será normal, y mostraremos en el únicamente 2 textos. A continuación vamos a ver su layout, el cual crearemos y, en mi caso, lo he llamado “fragmenta.xml”:

    		<?xml version="1.0" encoding="utf-8"?>
    		<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    		    android:layout_width="match_parent"
    		    android:layout_height="match_parent"
    		    android:gravity="center"
    		    android:orientation="vertical"
    		    android:padding="20dp" >
    		
    		    <TextView
    		        android:id="@+id/tvTexto1"
    		        android:layout_width="fill_parent"
    		        android:layout_height="wrap_content"
    		        android:text="Fragment 1"
    		        android:textSize="30sp"
    		        android:textStyle="bold" />
    		
    		    <TextView
    		        android:id="@+id/tvTexto2"
    		        android:layout_width="fill_parent"
    		        android:layout_height="wrap_content"
    		        android:text="Esto es un Fragmento normal"
    		        android:textSize="20sp" />
    		
    		</LinearLayout>
    		

    Como vemos tenemos 2 elementos TextView que muestran un texto estático sin ninguna funcionalidad, meramente lo usaremos para identificarlo.

    El código de esta clase no es nada del otro mundo tampoco, ya que lo único que haremos es sustituir el método onCreateView en el que retornaremos la View creada:

    		package sekth.droid.viewpager.Fragments;
    
    		import sekth.droid.viewpager.R;
    		import android.os.Bundle;
    		import android.support.v4.app.Fragment;
    		import android.view.LayoutInflater;
    		import android.view.View;
    		import android.view.ViewGroup;
    		
    		public class FragmentA extends Fragment {
    		
    			@Override
    			public View onCreateView(LayoutInflater inflater, ViewGroup container,
    					Bundle savedInstanceState) {
    				// TODO Auto-generated method stub
    				return inflater.inflate(R.layout.fragmenta, container, false);
    			}
    		
    		}
    		
  • FragmentB

    Este otro Fragment va a tener otra funcionalidad, ya que lo crearemos pero no heredará de la clase Fragment, sinó de la clase ListFragment, ya que vamos a poner una lista con elementos en el, parecido a la aplicación de la Play Store de Google. En mi caso lo he llamado “FragmentB”, y su archivo layout el cual vamos a crear lo llamaré “fragmentb.xml”. Vemos el código de su layout a continuación:

    		<?xml version="1.0" encoding="utf-8"?>
    		<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    		    android:layout_width="match_parent"
    		    android:layout_height="match_parent"
    		    android:orientation="vertical" >
    		
    		    <ListView
    		        android:id="@+id/android:list"
    		        android:layout_width="fill_parent"
    		        android:layout_height="wrap_content" />
    		
    		</LinearLayout>
    		

    Lo único que haremos es agregar un elemento ListView, el cual cargaremos mas tarde en el código del FragmentB.

    Por otro lado, el código java de la clase FragmentB, además de sustituir el método onCreateView del cuál ya hemos visto su utilidad, sustituiremos también el método onCreate, donde cargaremos el ListView. Vemos su código a continuación, el cuál explicaremos a continuación:

    		package sekth.droid.viewpager.Fragments;
    
    		import sekth.droid.viewpager.R;
    		import sekth.droid.viewpager.Clases.Content;
    		import android.os.Bundle;
    		import android.support.v4.app.ListFragment;
    		import android.util.Log;
    		import android.view.LayoutInflater;
    		import android.view.View;
    		import android.view.ViewGroup;
    		import android.widget.ArrayAdapter;
    		
    		public class FragmentB extends ListFragment {
    		
    			@Override
    			public View onCreateView(LayoutInflater inflater, ViewGroup container,
    					Bundle savedInstanceState) {
    				// TODO Auto-generated method stub
    				return inflater.inflate(R.layout.fragmentb, container, false);
    			}
    		
    			@Override
    			public void onCreate(Bundle savedInstanceState) {
    				// TODO Auto-generated method stub
    				super.onCreate(savedInstanceState);
    		
    				// Según el resultado, lo cargaremos con datos diferentes
    				int tipoLista = (int) (Math.random() * 3);
    		
    				switch (tipoLista) {
    				case 0:
    					setListAdapter(new ArrayAdapter<String>(getActivity(),
    							android.R.layout.simple_list_item_1, Content.smartphoneSO));
    					break;
    				case 1:
    					setListAdapter(new ArrayAdapter<String>(getActivity(),
    							android.R.layout.simple_list_item_1, Content.computerSO));
    					break;
    				case 2:
    					setListAdapter(new ArrayAdapter<String>(getActivity(),
    							android.R.layout.simple_list_item_1, Content.androidVersion));
    				}
    		
    			}
    		
    		}
    		

    Como vemos, en el método onCreate vamos a cargar las ListView. En mi caso, no quiero rellenar varias ListView con el mismo contenido, por lo que según el número aleatorio, se cargará con un contenido distinto.

    • Content

      En esta clase lo único que haré es crear 3 arrays de String, con contenido diferente para poder llenar las diferentes ListView. Su código es mostrado a continuación:

      				package sekth.droid.viewpager.Clases;
      
      				public class Content {
      				
      					public static final String[] smartphoneSO = { "Android", "Windows Phone",
      							"iOS", "Symbian", "Blackberry" };
      					public static final String[] computerSO = { "Windows", "Ubuntu", "Mac OSX",
      							"Kubuntu", "Solaris", "Chrome OS" };
      					public static final String[] androidVersion = { "Froyo", "Gingerbread",
      							"Ice Cream Sandwich", "Jelly Bean", "Cupcake", "Éclair",
      							"HoneyComb", "Key Lime" };
      				
      				}		
      				

      Como vemos no ofrece mucha utilidad, es solo para tener algún lugar donde crear datos para simular las listas.

  • MyPagerAdapter

    Para poder crear el ViewPager vamos a tener que apoyarnos en una clase que nos sirva de “adaptador”, como cualquier otro elemento que necesite adaptar una fuente de datos, ya sea un ListView o un Grid. Para ello vamos a crear una nueva clase llamada MyPagerAdapter, la cual va a heredar de la clase FragmentStatePagerAdapter. Aunque podemos heredar de otra clase, como por ejemplo FragmentPagerAdapter, la clase FragmentStatePagerAdapter trabaja mejor cuando tenemos una gran cantidad de Fragment que sean de tipo ListFragment, por tanto nos ofrece mayor rendimiento a la hora de implementarlo. Cuando una página no es visible, el Fragment es completamente destruido, guardando solo el estado del Fragment. Esto permite a requerir un uso menor de memoria.

    Vamos a ver su código a continuación:

    		package sekth.droid.viewpager.Adapter;
    
    		import java.util.List;
    		
    		import android.support.v4.app.Fragment;
    		import android.support.v4.app.FragmentManager;
    		import android.support.v4.app.FragmentStatePagerAdapter;
    		
    		public class MyPagerAdapter extends FragmentStatePagerAdapter {
    		
    			private List<Fragment> fragments;
    		
    			public MyPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
    				super(fm);
    				// TODO Auto-generated constructor stub
    				this.fragments = fragments;
    			}
    		
    			@Override
    			public Fragment getItem(int position) {
    				// TODO Auto-generated method stub
    				return fragments.get(position);
    			}
    		
    			@Override
    			public int getCount() {
    				// TODO Auto-generated method stub
    				return fragments.size();
    			}
    		
    		}
    		

    Vamos a ver cada uno de sus métodos:

    • public MyPagerAdapter(FragmentManager fm, List fragments)

      Este método es el constructor, y en el guardaremos una referencia a una lista de fragments que recogemos de los argumentos del método:

      				public MyPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
      					super(fm);
      					// TODO Auto-generated constructor stub
      					this.fragments = fragments;
      				}
      				
    • public Fragment getItem(int position)

      Este método se encarga de retornar la posición del Fragment actual:

      				@Override
      				public Fragment getItem(int position) {
      					// TODO Auto-generated method stub
      					return fragments.get(position);
      				}
      				
    • public int getCount()

      Este método será el encargado de retornar la cantidad de Fragment que tiene el adapter:

      				@Override
      				public int getCount() {
      					// TODO Auto-generated method stub
      					return fragments.size();
      				}
      				
  • ViewPagerActivity

    Esta es nuestra Activity, la cuál se creó junto a nuestro proyecto. Su archivo layout es el siguiente:

    		<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    		    android:id="@+id/viewPager"
    		    android:layout_width="match_parent"
    		    android:layout_height="match_parent" />
    		

    Como vemos solo tenemos un único elemento, el cuál sacamos de la librería de soporte.

    A continuación vamos a ver el código java de nustra Activity:

    		package sekth.droid.viewpager.Activity;
    
    		import java.util.ArrayList;
    		import java.util.List;
    		
    		import sekth.droid.viewpager.R;
    		import sekth.droid.viewpager.Adapter.MyPagerAdapter;
    		import sekth.droid.viewpager.Fragments.FragmentA;
    		import sekth.droid.viewpager.Fragments.FragmentB;
    		import android.os.Bundle;
    		import android.support.v4.app.Fragment;
    		import android.support.v4.app.FragmentActivity;
    		import android.support.v4.view.ViewPager;
    		
    		public class ViewPagerActivity extends FragmentActivity {
    		
    			MyPagerAdapter mPagerAdapter;
    			ViewPager mviewPager;
    			private List<Fragment> listaFragments;
    		
    			@Override
    			protected void onCreate(Bundle savedInstanceState) {
    				super.onCreate(savedInstanceState);
    				setContentView(R.layout.activity_view_pager);
    		
    				// Creamos una lista de Fragments
    				listaFragments = new ArrayList<Fragment>();
    				listaFragments.add(new FragmentB());
    				listaFragments.add(new FragmentA());
    				listaFragments.add(new FragmentB());
    				listaFragments.add(new FragmentA());
    				listaFragments.add(new FragmentB());
    		
    				// Creamos nuestro Adapter
    				mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(),
    						listaFragments);
    		
    				// Instanciamos nuestro ViewPager
    				mviewPager = (ViewPager) findViewById(R.id.viewPager);
    		
    				// Establecemos el Adapter
    				mviewPager.setAdapter(mPagerAdapter);
    			}
    		}
    		

    Lo único que haremos es crear una List en el que vamos a añadir diferentes Fragment. En este caso vamos a añadir 3 FragmentB y 2 FragmentA para que veamos las diferencias en la aplicación.

    Una vez tenemos la List creada, vamos a instanciar nuestra clase MyPagerAdapter pasandole como argumentos el FragmentManager que obtenemos mediante el método getSupportFragmentManager() y la List que acabamos de crear. Una vez hecho esto, instanciaremos nuestro ViewPager y le estableceremos nuestro Adapter.

Con esto tendremos finalmente nuestra aplicación montada y creada, tendremos una especie de Paginación, en la cual tendremos diferente información, y cada página tiene su propio ciclo de vida ya que son Fragment.

Podremos observar también que posiblemente que las listas que nos aparezcan no sean iguales, ya que mediante el uso de la superclase FragmentStatePagerAdapter hace que se destruyan los Fragment al no aparecer en pantalla, por tanto al volver a cargar los ListView, se vuelve a ejecutar el código en el cual se asigna una fuente de datos. Esto podremos resolverlo guardando el estado del Fragment y comprobando antes de crearlo si hay algo guardado para reestablecerlo.

Vemos unas capturas de pantalla de como queda finalmente:

Lista 1 Fragment A Lista 2

Ahora vemos un video donde podemos ver finalmente la aplicación en ejecución:



Con esta entrada hemos aprendido a implementar el ViewPager junto a Fragment en nuestros proyectos, lo cual nos puede ser de mucha utilidad.

El código de ejemplo se puede descargar de aquí.

Sin más, cualquier aporte o corrección es bienvenida.

Saludos!!!

  • luis

    Hola buenas… entiendo que en este ejemplo podemos colocar cualquier .xml (FragmentA), pero cuando queremos darle funciones a ese .xml, es decir, en el xml añado un Button y quiero que al pulsar me abra una Activity, que me falta…

    No se si me he explicado, soy novato, bueno más para atrás de novato.

  • Buenos Luis,

    Es fácil, no tiene complicación ninguna, ya que los Fragment no dejan de ser otra cosa que mini-Activities, por tanto tienen una forma de trabajar parecida respecto a Button, EditText o cualquier otro tipo de elemento. Te pongo un ejemplo sobre un FragmentC en el que puedes encontrar un Button que lanza un mensaje por medio de Toast:

    Su archivo layout, llamado fragmentc.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <Button
            android:id="@+id/btnBoton"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Boton" />
    
    </LinearLayout>
    

    Su clase FragmentC:

    public class FragmentC extends Fragment {
    	private Button btnBoton;
    
    	@Override
    	public View onCreateView(LayoutInflater inflater, ViewGroup container,
    			Bundle savedInstanceState) {
    		// TODO Auto-generated method stub
    		View v = inflater.inflate(R.layout.fragmentc, container, false);
    		btnBoton = (Button) v.findViewById(R.id.btnBoton);
    		return v;
    	}
    
    	@Override
    	public void onActivityCreated(Bundle savedInstanceState) {
    		// TODO Auto-generated method stub
    		super.onActivityCreated(savedInstanceState);
    		btnBoton.setOnClickListener(new View.OnClickListener() {
    
    			@Override
    			public void onClick(View arg0) {
    				// TODO Auto-generated method stub
    				Toast.makeText(getActivity(), "Ha pulsado un botón",
    						Toast.LENGTH_SHORT).show();
    			}
    		});
    	}
    
    }
    

    Como puedes ver si ejecutas esto con el código del ejemplo, aunque agregandolo a la lista mediante:

    listaFragments.add(new FragmentC());
    

    La aplicación se ejecuta, y el botón funciona como si de una Activity normal se tratase.

    Espero que te sea de ayuda,

    Saludos!!

    • luis

      Me parece que mis diseños van a dar un giro radical gracias a esto… ya sólo me falta entender como dar cualquier actividad, sea un simple button (en mi caso utilizo todo el Linealayout), un mapview, Mediaplayer, etc… y bueno ponerle TitlePager al ViewPager, vamos ponerle corbata al traje… pero ya estoy mucho más cerca de la ideaprincipal.

      Muchas gracias

  • Buenas,

    Tengo una duda, estoy trabajando con ViewPager y ViewPagerIndicator. Tengo dos fragments que extienden los dos de ListFragment. La carga de datos la tengo puesta en el Oncreate de cada Fragment.
    Antes tenia mi MyFragmentAdapter extendido de FragmentPagerAdapter pero he visto tu articulo y ahora lo tengo como en el ejemplo con FragmentStatePagerAdapter. Parece que funciona mejor, pero sigo teniendo el mismo problema que antes, que es, que a la hora de ejecutar la aplicación me carga los datos de los dos fragment y son datos que tiran de conexión a Internet por lo que ralentiza mucho la aplicación. Pensaba que al utilizar en mi MyFragmentAdapter la extensión de FragmentStatePagerAdapter se iba a resolver esto. Es decir, que la aplicacion cargue solo los datos del primer Fragment y cuando se clique en el segundo que los cargue.

    ¿Alguna sugerencia?

    Felicidades por el articulo. Gracias.

    • Buenas Manuel, perdona que no te haya podido contestar antes.

      Por lo que me comentas no parece un caso de que te vaya lento por el adapter, sino porque al recuperar los datos via internet, dependerás de la conexión a nternet que tengas o del servidor para que esto vaya mas rápido o mas lento.

      Si lo que tienes son 2 Fragments en un FragmentStatePagerAdapter, o bien puedes cargar los datos únicamente del que se está visualizando, por lo que algunos de los métodos que tienen los Fragment te puede servir, o bien controlando el fragment que está siendo visto mediante el uso de la interface “OnPageChangeListener”, la cual indica a la Activity que página del FragmentPagerStaterAdapter está siendo cambiado.

      Otra alternativa es, tener estos elementos descargados antes de pasar a esa activity (en caso de que sea secundaria por ejemplo), o al bajarlos por primera vez cachear estos datos para tenerlos luego con mas rápido acceso.

      Espero haber sido de alguna ayuda.

      Saludos!

      • Manuel

        Gracias David por responderme.
        Al final me funciono mejor poner un AsyncTask en cada Fragment. Ahora estoy cacheando los datos, ya que aunque los dos Fragment me cargan deprisa, al hacer scroll se relantiza mucho.

        Lo dicho, gracias por la respuesta.
        Saludos.!!