Tip Android #17: ListView «Back to top» (volver al inicio)

15/04/2013
android tip

Al mostrar un ListView con un elevado número de elementos podría ser una buena idea dar la posibilidad al usuario de regresar directamente a la posición inicial sin tener que volver a deslizar toda la lista. En este tip se va a mostrar cómo implementar esta funcionalidad mediante la pulsación del último elemento de un ListView (en realidad su footer).

Para el ejemplo vamos a utilizar una lista estándar que simplemente muestre cadenas y a una Activity que herede de ListActivity. Como es habitual, en este artículo se mostrará el código más relevante y al final se incluirá el enlace para descargar el proyecto completo. El layout será el siguiente:

<?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">f

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

Para el footer crearemos el layout que más nos convenga (un botón, una imagen, lo que queramos). He optado por un TextView con una imagen a su izquierda (Nota: es más eficiente incluir la imagen en el TextView que definirla en un ImageView independiente). Este layout se llamará back_to_top_list_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_back_top"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/back_to_top_selector"
    android:gravity="center" >

    <TextView
        android:id="@+id/textView_back_top"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/back_to_top"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:drawableLeft="@drawable/top" />

</LinearLayout>

Para distinguir el footer del resto de elementos de la lista se va a usar el siguiente drawable (back_to_top_selector.xml)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true">
        <shape>
            <solid android:color="#c0c1c0" />
        </shape>        
    </item>
    
    <item android:state_focused="true">
        <shape>
            <solid android:color="#c0c1c0" />
        </shape>
    </item>
    
    <item>
        <shape>
            <solid android:color="#929392" />
        </shape>
     </item>

</selector>

Y ahora, vamos con la Activity. El código es muy sencillo y está comentado. Lo más relevante es el método que se usará para volver al inicio de la lista. Tenemos dos opciones principales:

  1. setSelection: Muestra como primer elemento de la lista el que le indiquemos, en nuestro caso el cero. El «problema» de este método es que la transición es instánea sin animación alguna por lo que el efecto es un tanto abrupto.
  2. smoothScrollToPositionFromTop:Esta solución es más sofisticada y potente, y el desplazamiento lo hace suavemente, incluso hay una versión del método en la que se puede especificar la duración del mismo. El problema es que este método sólo está disponible a partir de Honeycomb, por lo que si nuestra aplicación es compatible con versiones anteriores deberemos detectar la versión de Android que ejecuta la aplicación para que este método sólo se utilice si está disponible. Es lo que vamos a en este tip (el proyecto está configurado para usar la API 17 (4.2) pero es compatible con Android 2.1)
  3. public class MainActivity extends ListActivity
    {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		List<String> items = new ArrayList<String>(30);
    		
    		for (int i =0;i<30;i++)
    		{
    			items.add("Item " + i);
    		}
    		
    		//obtenemos el footer y establecemos su listener
    		View backToTop = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.back_to_top_list_view, null, false);
    		backToTop.setOnClickListener(new OnClickListener() {
    			
    			@SuppressLint("NewApi")
    			@Override
    			public void onClick(View v)
    			{
    				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) //método estándar
    				{
    					getListView().setSelection(0);
    				}
    				else //sólo disponible a partir de Honeycomb
    				{
    					getListView().smoothScrollToPositionFromTop(0, 0);
    					//se puede especificar la duración de la animación en ms
    					//getListView().smoothScrollToPositionFromTop(0, 0,1000);
    				}
    				
    			}
    		});
    		//el footer siempre se añade antes que el adapter
    		getListView().addFooterView(backToTop);
    		
    		setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,android.R.id.text1, items));
    	}
    }
    

    El resultado final en Jelly Bean donde podemos apreciar el deslizamiento suave hasta el inicio de la lista:



    En Android 2.1, se aplica el método setSelection y el desplazamiento es inmediato y menos estético:


    Hasta aquí, todo correcto pero ¿qué pasaría si la lista contiene menos elementos de los que caben en pantalla? El resultado sería muy antiéstético:

    listView back to top

    La solución consistiría en mostrar el footer sólo en los casos en que el Listview contenga más elementos de los que caben en pantalla, lo que depende de la resolución y tamaño del dispositivo. Lamentablemente no podemos saber cuántos elementos caben en pantalla hasta que el ListView es renderizado, por lo que por defecto no mostraremos el footer y con el código resaltado haciendo uso del método post y de hilos decidir si hay que mostrarlo. El resultado final (el proyecto de ejemplo descargable) quedará tal que así:

    public class MainActivity extends ListActivity
    {
    	private View backToTop;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		List<String> items = new ArrayList<String>(30);
    		
    		for (int i =0;i<30;i++)
    		{
    			items.add("Item " + i);
    		}
    		
    		//obtenemos el footer y establecemos su listener
    		backToTop = ((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.back_to_top_list_view, null, false);
    		backToTop.setOnClickListener(new OnClickListener() {
    			
    			@SuppressLint("NewApi")
    			@Override
    			public void onClick(View v)
    			{
    				if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) //método estándar
    				{
    					getListView().setSelection(0);
    				}
    				else //sólo disponible a partir de Honeycomb
    				{
    					getListView().smoothScrollToPositionFromTop(0, 0);
    					//se puede especificar la duración de la animación ne ms
    					//getListView().smoothScrollToPositionFromTop(0, 0,1000);
    				}				
    			}
    		});
    		//el footer siempre se añade antes que el adapter, luego se puede mostrar y ocultar cuando se quiera
    		getListView().addFooterView(backToTop);
    		
    		setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,android.R.id.text1, items));
    		//no se muestra el footer haste que no sepamos si es necesario
    		getListView().removeFooterView(backToTop);
    		
    		getListView().post(new Runnable() { //se averigua si el footer es necesario, esto es, hay elementos que no se muestran en pantalla
    			   public void run() {
    				   int visible = MainActivity.this.getListView().getLastVisiblePosition();
    				   int total = MainActivity.this.getListAdapter().getCount();
    				   if (visible + 1 < total)
    				   {
    					   MainActivity.this.getListView().addFooterView(MainActivity.this.backToTop);
    				   }
    			   }
    			});
    	}
    }
    

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

    << TIPS ANDROID

Deja una respuesta

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. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.