Diseño Android: ViewPager


VERSIONES

  1. 08/12/2013 (Primera publicación)
  2. 10/08/2015: Actualizado a Lollipop y Android Studio

  3. android

    Un ViewPager es un ViewGroup que permite desplazarnos por distintos layouts o “páginas” dentro de una misma Activity simplemente deslizando las páginas a la derecha o izquierda. Este widget no está presente en la API de Android y se encuentra disponible en la librería de compatibilidad lo que permite su utilización en cualquier versión de Android.

    Como caso práctico de ViewPager, podemos poner Google Play aunque son miles las aplicaciones que siguen en este diseño.

    google play

    En este tutorial veremos en primer lugar cómo utilizar un ViewPager (sin Fragments) y en el siguiente configuraremos el indicador de página utilizando la excelente librería open source ViewPagerIndicator.

    Requisitos: Conocimientos básicos de Android SDK (LinearLayot, ListView, estilos).

    Proyecto de ejemplo

    Se va a utilizar como base para el artículo un proyecto estándar compatible Android 2.1 y como target la última versión de Android (Lollipop Api 22). En primer lugar hay que incluir en nuestro proyecto la librería de compatibilidad, aunque si creamos un proyecto nuevo con el asistente en Eclipse ADT o Android Studio probablemente ya la tengamos disponible.

    Eclipse ADT

    Podemos descargar la última versión de la librería de compatibilidad que necesitamos desde Android SDK Manager en la sección de extras bajo el nombre “Android Support Library”.

    viewpager

    Ubicando el puntero del ratón sobre la entrada podemos ver la ubicación en la que se ha descargado. A continuación, copiamos el fichero android-support-v4.jar al directorio libs en la raiz de nuestro proyecto.

    Android Studio\Gradle

    Simplemente añadimos la dependencia en el fichero /app/build.gradle

    dependencies {
        ...
        compile "com.android.support:support-v4:18.0.+"
    }
    

    El proyecto de ejemplo contará con cuatro “páginas” y cada una de ellas se corresponde con un layout estándar definido en xml en /res/layout sin ninguna particularidad o requisito especial. Todas excepto la tercera serán iguales y simplemente mostrarán el número de página en su interior. Por ejemplo, esta es la número dos:

    page_two.xml

    <?xml version="1.0" encoding="utf-8">
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@drawable/page"
        android:gravity="center" >
    
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/two"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@android:color/>
    
    </LinearLayout>
    

    La tercera página mostrará un ListView para hacer el ejemplo un poco más elaborado y comprobar cómo un ViewPager puede mostrar cualquier View y es totalmente “agnóstico” en lo que respecta a su contenido.

    page_three.xml

    <?xml version="1.0" encoding="utf-8">
    
    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/page"
        android:padding="5dp" 
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:divider="@android:color/darker_gray"
        android:dividerHeight="1dp">
    
    </ListView>
    

    El ListView mostrará un simple texto por lo que no será necesario crear un Adapter. No obstante, para darle formato al texto utilizaremos el siguiente layout

    textview.xml

    <?xml version="1.0" encoding="utf-8">
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/textView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="5dp"
        android:paddingRight="3dp"
        android:paddingTop="5dp"
        android:background="@drawable/item"
        android:textAppearance="@android:style/TextAppearance.Medium"
        android:textColor="@android:color/black" />
    

    Vamos a mostrar cada página dentro de un marco con bordes redondeados. El margen de la página con respecto a la pantalla (aunque no entre ellas, este efecto se puede conseguir fácilmente “anidando” cada página dentro un LinearLayout) lo definiremos más adelante en el propio ViewPager. Este borde se declara mediante el drawable “page”, ubicado en /res/drawable/page.xml

    <?xml version="1.0" encoding="UTF-8">
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle" >
    
        <stroke
            android:width="3dp"
            android:color="@android:color/black" />
    
        <solid android:color="@android:color/white" />
    
        <corners
            android:bottomLeftRadius="7dp"
            android:bottomRightRadius="7dp"
            android:topLeftRadius="7dp"
            android:topRightRadius="7dp" />
    
    </shape>
    

    Y ahora, vamos con la pantalla principal y única de la aplicación. De momento la dejamos vacia, en ella ubicaremos nuestro ViewPager.

    main.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" >
    
    
    </LinearLayout>
    

    Por último, tendremos una única Activity que mostrará el layout que contendrá el ViewPager.

    package com.danielme.blog.android.viewpager;
    
    
    import com.danielme.blog.android.viewpager.R;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    /**
     * 
     * @author danielme.com
     *
     */
    public class MainActivity extends Activity
    {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    	}
    }
    

    El proyecto tiene la siguiente estructura

    viewpager

    ViewPager

    Un ViewPager se añade a cualquier layout como un widget más, en este caso tal y como se ha comentado se establecerá un padding y color de fondo para que las páginas tengan un margen con respecto a la pantalla. Esto dependerá del estilo que queramos para nuestra aplicació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" >
    
        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" 
            android:paddingTop="5dp"
            android:paddingLeft="8dp"
            android:paddingRight="8dp"
            android:paddingBottom="5dp"
            android:background="@android:color/darker_gray"/>
    
    </LinearLayout>
    

    Lo más interesante se encuentra en la Activity en la que tenemos que recuperar el ViewPager y asignarle una subclase de la clase abstracta PagerAdapter, clase interna de la Activity para que pueda acceder a sus atributos, cuyo principal cometido será indicar al ViewPager qué vista (View) debe mostrar para cada página. En el caso de que en lugar de View usemos Fragments, la clase a heredar puede ser FragmentAdapter o FragmentStatePage. La primera de ellas mantiene los Fragments (páginas) en memoria, mientras que la segunda no y la documentación recomienda su uso para ViewPager con numerosas páginas lo que permite disminuir el consumo de memoria a costa de un pequeño sobrecoste al cambiar de página. En cualquier caso, en este tutorial vamos a centrarnos en View tal y como se comentó en la introducción.

    La Activity quedará tal que así, luego la analizaremos:

    package com.danielme.blog.android.viewpager;
    
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.support.v4.view.PagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.ArrayAdapter;
    import android.widget.LinearLayout;
    import android.widget.ListView;
    import android.widget.Toast;
    
    /**
     * 
     * @author danielme.com
     * 
     */
    public class MainActivity extends Activity
    {
    	private ViewPager viewPager;
    
    	private LinearLayout page1;
    	private LinearLayout page2;
    	private ListView page3;
    	private LinearLayout page4;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    		viewPager = (ViewPager) findViewById(R.id.pager);
    		viewPager.setAdapter(new MainPageAdapter());
    	}
    
    	class MainPageAdapter extends PagerAdapter
    	{
    
    		@Override
    		public int getCount()
    		{
    			return 4;
    		}
    
    		@Override
    		public Object instantiateItem(ViewGroup collection, int position)
    		{
    			View page = null;
    			switch (position)
    			{
    			case 0:
    				if (page1 == null)
    				{
    					page1 = (LinearLayout) LayoutInflater.from(MainActivity.this).inflate(R.layout.page_one, null);
    				}
    				page = page1;
    				break;
    			case 1:
    				if (page2 == null)
    				{
    					page2 = (LinearLayout) LayoutInflater.from(MainActivity.this).inflate(R.layout.page_two, null);
    				}
    				page = page2;
    				break;
    			case 2:
    				if (page3 == null)
    				{
    					page3 = (ListView) LayoutInflater.from(MainActivity.this).inflate(R.layout.page_three, null);
    					initListView();
    				}
    				page = page3;
    				break;
    			default:
    				if (page4 == null)
    				{
    					page4 = (LinearLayout) LayoutInflater.from(MainActivity.this).inflate(R.layout.page_four, null);
    				}
    				page = page4;
    				break;
    			}
    
    			((ViewPager) collection).addView(page, 0);
    
    			return page;
    		}
    
    		@Override
    		public boolean isViewFromObject(View view, Object object)
    		{
    			return view == object;
    		}
    
    		@Override
    		public void destroyItem(View collection, int position, Object view)
    		{
    			((ViewPager) collection).removeView((View) view);
    		}
    
    		private void initListView()
    		{
    			ListView listView = (ListView) page3.findViewById(R.id.listView1);
    			String[] items = new String[50];
    			for (int i = 0; i < 50; i++)
    			{
    				items[i] = "Item " + i;
    			}
    			page3.setAdapter(new ArrayAdapter<String>(MainActivity.this, R.layout.textview, items));
    			page3.setOnItemClickListener(new OnItemClickListener() {
    
    				@Override
    				public void onItemClick(AdapterView<> parent, View view, int position, long id)
    				{
    					Toast.makeText(MainActivity.this, (String) parent.getItemAtPosition(position), Toast.LENGTH_SHORT).show();
    				}
    			});
    
    		}
    	}
    
    }
    
    

    Hay que implementar cuatro métodos, entre los que destacan dos:

    • getCount: Simplemente devuelve el número de páginas.
    • instantiateItem: devuelve la página solicitada por el usuario, siendo la primera la número cero. En nuestro caso, sólo creamos la vista la primera vez y guardamos su referencia en la Activity. Obsérvese que el ListView se utiliza como cualquier ListView, sin nada en particular que sea consecuencia del hecho de estar dentro de un ViewPager.

    Hay que destacar que cuando el ViewPager tiene que instanciar una página siempre instancia las dos adyacentes, y el resto cuando se soliciten. Podemos ver fácilmente este comportamiento con un breakpoint, y es importante tenerlo en cuenta porque al instanciarse por primera vez el ViewPager en nuestro ejemplo se solicitan las 3 primeras páginas y es posible que una de ellas que no sea la primera realice operaciones costosas como obtener datos desde Internet.

    Para evitar que la tercera página se instancie en el inicio usando como pequeño hack un boolean que indique si es la primera vez que se solicita dicha página para ignorar su creación hasta que el usuario la solicite explícitamente. Asimismo, se puede ampliar el número de páginas adyacentes a la solicitada a instanciar con el método setOffscreenPageLimit pero por las pruebas que hecho sólo permite aumentar el mínimo por defecto y no reducirlo. En nuestro ejemplo, usaríamos el siguiente código para que todas se instancien al crearse el ViewPager:

    viewPager.setOffscreenPageLimit(3);

    Otras opciones interesantes que nos brinda ViewPager son las siguientes:

    • Seleccionar programáticamente una página con el método setCurrentItem.
    • Obtener la página actual con el método getCurrentItem
    • Tener el control total de la paginación con el listener ViewPager.OnPageChangeListener. Por ejemplo a continuación se añade a la Activity una implementación de este listener que muestra un toast con la página seleccionada. Recordar nuevamente que la primera página es la número cero.
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    		viewPager = (ViewPager) findViewById(R.id.pager);
    		viewPager.setAdapter(new MainPageAdapter());
    		viewPager.addOnPageChangeListener(new OnPageChangeListener() {
    			
    			@Override
    			public void onPageSelected(int position)
    			{
    				Toast.makeText(MainActivity.this, "page " + (position + 1), Toast.LENGTH_SHORT).show();				
    			}
    			
    			@Override
    			public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
    			{
    						
    			}
    			
    			@Override
    			public void onPageScrollStateChanged(int state)
    			{
    					
    			}
    		});
    	}
    

    El resultado final en Jelly Bean 4.1 incluyendo este último listener.


    Obsérvese cómo podemos cambiar de página, pero no tenemos ningún indicador visual que nos indique en qué páginas estamos o cuántas existen. Vamos a solucionar esto en el próximo artículo con ViewPagerIndicator.

    Código completo

    El proyecto completo para Eclipse ADT se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.

4 Responses to Diseño Android: ViewPager

  1. Pingback: Android – Trabajando con ViewPager

  2. jose dice:

    Hola Daniel!!

    Tengo una duda, a ver si tu me puedes ayudar. Estoy haciendo un viewpager siguiendo tu tutorial. El caso es que lo he hecho de tal manera que lo uso como una especie de picker para seleccionar varias opciones que se ven en la pantalla. Mi problema es que no consigo que las paginas cambien al tocarlas sólo consigo pasarlas con el gesto “swipe”. Sería posible cambiar las páginas de un viewpager tocándolas y deslizándolas?

    Muchas gracias por adelantado!!

    • danielme.com dice:

      Puedes navegar hacia cualquier página con el método viewPager.setCurrentItem(3). Sería cuestión de llamar a este método en el listener del botón o similar.

      • jose dice:

        Hola Daniel,

        Muchas gracias por tu respuesta, me sido muy util. He conseguido pasar de item pulsando un botón. Ahora lo que no soy capaz de hacer es que los item se puedan seleccionar pulsando en los mismos. A ver si soy capaz de explicarme. Tengo un viewpager al cual le he dado un aspecto para que se puedan ver varias opciones en la pantalla “1a, 1b, 1c, 2, 2a”. lo que me gustaría conseguir es moverme por estas opciones pulsándolas

        Muchas gracias de nuevo

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: