La columna 80

El blog técnico-personal de Ángel J. Vico… en español

Especializaciones del diseño FrameLayout: ScrollView y HorizontalScrollView

Posted by Ángel J. Vico en 1 de septiembre de 2012

En el post anterior vimos las características del diseño FrameLayout y ejemplos de cómo utilizarlo en nuestras aplicaciones. Es un diseño muy sencillo y muy eficiente, motivo por el cual se usa como base para otras muchas vistas.

En este post me voy a centrar en dos de las más parecidas: ScrollView y HorizontalScrollView. Ambas actúan como contenedores genéricos añadiendo una funcionalidad muy concreta al FrameLayout: desplazamiento.

Veamos cómo funcionan.

ScrollView

ScrollView es una de esas vistas que está a medio camino entre el diseño y el widget. Por un lado, se puede considerar diseño, dado que puede incluir otras vistas e impone ciertas reglas para ubicarlas y dimensionarlas. Pero, por otro lado, también tiene algo de interfaz gráfica (la barra de desplazamiento) y permite interacción con el usuario, como los widgets.

La clasifiquemos como la clasifiquemos, ScrollView es una vista muy interesante: le añade desplazamiento vertical a FrameLayout. Esto nos permite mostrar un contenido de tamaño (altura) superior al que cabría en la pantalla proporcionando al usuario la posibilidad de desplazarse a lo largo de él. Teniendo en cuenta la cantidad de pantallas con tamaños diferentes que pueden tener los dispositivos Android, está claro que esta vista es una de las que más utilizaremos en nuestras aplicaciones.

Veamos cómo usar ScrollView en un diseño XML:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:scrollbars="vertical">
   <LinearLayout
      android:layout_width="fill_parent"
      android:orientation="vertical"
      android:layout_height="fill_parent"
      android:paddingRight="5px" >
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="1" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="2" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="3" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="4" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="5" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="6" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="7" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="8" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="9" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="10" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="11" />
      <EditText
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:text="12" />
   </LinearLayout>
</ScrollView>

En el ejemplo se muestra el que seguramente será el uso más frecuente de ScrollView: como contenedor de un LinearLayout vertical. Dentro del LinearLayout se incluirán el resto de vistas de la interfaz de usuario. En el ejemplo, unos cuantos EditText que ocupan más del espacio disponible de la pantalla.

Al LinearLayout se le ha añadido un padding derecho para que la barra de desplazamiento se vea mejor al no superponerse a los cuadros de texto. Hay que tener en cuenta que normalmente la barra de desplazamiento no aparece hasta que no desplazamos el dedo por la pantalla. Puedes obtener más información sobre el padding en este post.

La interfaz que se genera es esta:

Actividad con ScrollView - Vista superiorActividad con ScrollView - Vista inferior

Veamos ahora cómo generar esa misma interfaz mediante código:

   @Override
   public void onCreate (Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);

      LinearLayout ll = new LinearLayout(this);
      ll.setOrientation(LinearLayout.VERTICAL);
      ll.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

      for (int i = 0; i < 12; i++)
      {
         ll.addView(new EditText(this), new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
      }

      ScrollView sv = new ScrollView(this);
      sv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
      sv.setVerticalScrollBarEnabled(true);
      sv.addView(ll);

      setContentView(sv);
   }

Esta vez he utilizado una variante del método addView que hemos usado otras veces, que permite especificar los parámetros de diseño de la vista que se añade en la misma llamada. Eso me permite crear directamente cada EditText en la misma sentencia en la que lo añado al ScrollView. Como todos los EditText del ejemplo son iguales, los creo y añado en un bucle.

Nota

En este código no se añaden los textos numéricos que se han incluido en el XML (y que se pueden observar en la captura). Para añadirlos habría que crear primero cada objeto EditText (fuera de la llamada a addView) y añadirle luego el texto con setText. El contenido del bucle for quedaría así:

         EditText et = new EditText(this);
         et.setText(Integer.toString(i + 1));
         ll.addView(et, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));

Propiedades

ScrollView sólo añade una propiedad a las que ya proporciona FrameLayout: android:fillViewport (setFillViewport). Ya hemos visto que ScrollView permite ver el contenido de una vista que tiene una altura superior a la que la pantalla de nuestro dispositivo es capaz de mostrar, pero, ¿qué pasa si el tamaño de la vista es inferior al del espacio disponible?

En este caso, ScrollView se comporta como cualquier otra vista, en función del valor que le demos al parámetro android:layout_height. Lo habitual, en el caso de ScrollView es que usemos el valor wrap_content, lo que hace que el tamaño del diseño se ajuste a su contenido. En esta situación, si el contenido es mayor que el espacio disponible, se mostrará todo lo que quepa permitiendo el desplazamiento. Y si el contenido es menor, el tamaño del diseño se reducirá.

Puede ocurrir que eso no sea lo que queremos. Quizás queramos que alguna vista que hayamos situado en la parte inferior del ScrollView quede alineada con la parte inferior de la pantalla aunque el resto del contenido sea menor que el espacio disponible. Usando wrap_content esas vistas quedarían más arriba.

Un primer impulso puede ser usar fill_parent. Pero eso no produce totalmente el efecto deseado. De hecho, no produce ningún efecto al usarlo en combinación con ScrollView, porque su contenido no tiene el tamaño de la pantalla como límite.

Para alcanzar nuestro objetivo necesitamos una combinación de la propiedad android:fillViewport de ScrollView junto con el parámetro android:layout_weight de LinearLayout. Este último parámetro, que explicaré con más detalle en el post sobre LinearLayout, sirve para que las vistas se repartan el espacio sobrante, hasta ocupar el tamaño máximo que puede tener el LinearLayout. Veamos cómo funciona esta combinación:

Texto corto y sin fillViewportTexto corto con fillViewport y fill_parentTexto corto con fillViewport y layout_weightTexto largo - parte superiorTexto largo - parte inferior

En las capturas se muestra un ScrollView que contiene un LinearLayout vertical. Dentro del LinearLayout se incluyen un TextView y un Button. El objetivo es que el botón quede siempre en parte inferior de la pantalla, pero que no se muestre hasta que el usuario desplace el texto hasta el final si resulta ser más largo de lo que se puede mostrar de una sola vez.

Cuando el texto es corto lo normal es que el botón quede justo debajo del texto, como se muestra en la primera captura. Da igual que se use el valor fill_parent para el parámetro android:layout_height del TextView o del propio LinearLayout. Ni siquiera usar el parámetro android:layout_weight en el TextView logra, por sí solo, que se expanda la vista que contiene el texto y desplace el botón hasta la parte inferior de la pantalla.

Por otro lado, si usamos únicamente android:fillViewport en el ScrollView tampoco obtendremos resultado alguno (se seguirá comportando como en la primera captura), salvo que usemos también fill_parent en el parámetro android:layout_height del TextView. En ese caso ocurrirá lo que se muestra en la segunda captura, que tampoco es lo que queremos, porque el botón queda inaccesible.

La única forma de conseguir nuestro objetivo es poner a true la propiedad android:fillViewport del ScrollView y usar el parámetro android:layout_weight en el TextView. En ese caso se verá como en la tercera captura, con el botón en la parte inferior, lo que resulta mucho más estético.

Todo esto sólo tiene utilidad cuando el texto es más corto que lo que la pantalla permite mostrar. Si el texto es largo el botón sólo resultará visible cuando el usuario desplace el ScrollView, y siempre quedará en la parte inferior (como se puede observar en las dos últimas capturas).

La barra de desplazamiento

Las barras de desplazamiento también se controlan mediante propiedades que se traducen en atributos de la vista en el XML. Curiosamente, las propiedades relacionadas con barras de desplazamiento están definidas directamente en la clase View, por lo que, en principio, todas las vistas disponen de ellas.

Esto no significa, obviamente, que todas las vistas tengan funcionalidad de desplazamiento, ni mucho menos. Podemos forzar el uso de la barra de desplazamiento en, por ejemplo, un Button. Incluso podemos conseguir que se muestre dicha barra, haciendo que el contenido sea muy grande. Pero en ningún caso podremos desplazar ese contenido. Y tampoco es que tenga mucho sentido tratar de hacerlo, dicho sea de paso.

Además, aunque la vista pueda tener barras de desplazamiento, puede que no soporte todos los posibles valores de algunas de las propiedades. Por ejemplo, como ScrollView sólo implementa desplazamiento vertical, tratar de mostrar la barra de desplazamiento horizontal no tiene ningún efecto.

Veamos las propiedades que permiten gestionar las barras de desplazamiento:

  • android:scrollbars (setHorizontalScrollBarEnabled, setVerticalScrollBarEnabled): el atributo puede recibir tres valores: none, horizontal o vertical, que significa que no se muestra ninguna barra, sólo la horizontal o sólo la vertical (respectivamente). Los dos últimos se pueden usar simultáneamente separándolos con |, para mostrar ambas barras. En Java hay un método para indicar si cada barra se muestra o no.

    En ScrollView, el valor predeterminado es vertical. O sea, que la barra siempre se muestra. Si no queremos que aparezca hay que usar none. El valor horizontal, como ya hemos comentado, no tiene ningún efecto en ScrollView.

  • android:scrollbarStyle (setScrollBarStyle): permite definir el estilo de la barra, lo que afecta también a su posición. Puede tener cuatro valores (entre paréntesis se indica la constante para el método Java equivalente):

    • insideOverlay (SCROLLBARS_INSIDE_OVERLAY): dentro del contenido (en el interior del padding) y superpuesta

    • insideInset (SCROLLBARS_INSIDE_INSET): dentro del contenido, pero sin superponerse

    • outsideOverlay (SCROLLBARS_OUTSIDE_OVERLAY): en el borde de la vista (fuera del padding) y superpuesta

    • outsideInset (SCROLLBARS_OUTSIDE_INSET): en el borde de la vista, pero sin superponerse.

    Las siguientes capturas muestran el efecto de cada una de las opciones cuando tenemos un padding en el ScrollView:

    ScrollView con estilo insideOverlay y con paddingScrollView con estilo insideInset y con paddingScrollView con estilo outsideOverlay y con paddingScrollView con estilo outsideInset y con padding

    Como se puede observar, los valores inside sitúan la barra de desplazamiento en el contenido, dejando todo el padding a la derecha. Los valores outside, sin embargo, sitúan la barra en el exterior del ScrollView, dejando el padding a la izquierda.

    La diferencia entre los valores overlay y los inset también es bastante obvia: cuando se usa overlay la barra se muestra sobre el área en el que se coloca (sobre el contenido del ScrollView en el caso de insideOverlay y sobre el propio padding en el caso de outsideOverlay). Usar inset provoca la creación un espacio propio para la barra de desplazamiento, moviendo y cambiando el tamaño del contenido del ScrollView.

    En las capturas se puede observar cómo los valores inset provocan que el padding derecho parezca mayor de lo que es, al sumársele el espacio destinado a la barra. Esto es algo que habrá que tener en cuenta si la barra sólo se muestra durante el desplazamiento, porque el contenido parecerá “descentrado” cuando la barra esté oculta.

    Si no tenemos padding, no hay ninguna diferencia entre situar la barra inside u outside, como se puede apreciar en las siguientes capturas:

    ScrollView con estilo insideOverlay y sin paddingScrollView con estilo insideInset y sin paddingScrollView con estilo outsideOverlay y sin paddingScrollView con estilo outsideInset y sin padding

  • android:scrollbarFadeDuration: de forma predeterminada, la barra de desplazamiento sólo se muestra cuando se está arrastrando el contenido de la vista, desapareciendo poco tiempo después. Este atributo permite definir cuánto tiempo (en milisegundos) permanece visible la barra tras el desplazamiento.

  • android:fadeScrollbars (setScrollbarFadingEnabled): permite activar o desactivar el desvanecimiento de la barra de desplazamiento. Si se usa con el valor false, las barras se muestran todo el tiempo. Teóricamente se debería poder conseguir el mismo efecto usando el valor cero en android:scrollbarFadeDuration, pero no siempre funciona.

  • android:scrollbarSize: permite establecer el ancho de la barra de desplazamiento. Es de tipo dimensión, pero no funciona correctamente en algunas versiones de Android: http://code.google.com/p/android/issues/detail?id=14317.

HorizontalScrollView

Este widget es idéntico a ScrollView, salvo porque el desplazamiento lo realiza horizontalmente en lugar de verticalmente. El resto del comportamiento es idéntico y las propiedades que permiten personalizar ScrollView funcionan de forma idéntica en HorizontalScrollView, por lo que no hay mucho más que añadir aquí.

Doble desplazamiento

Android no dispone de una vista que realice ambos desplazamientos simultáneamente.

Una solución rápida puede ser incluir un HorizontalScrollView dentro de un ScrollView (o viceversa). Sin embargo, eso no provoca totalmente el efecto deseado. El problema es que la barra de desplazamiento forma parte de la vista. Si el HorizontalScrollView está dentro de un ScrollView y su tamaño vertical es superior a lo que se puede visualizar, la barra de desplazamiento del HorizontalScrollView sólo será visible cuando se esté mostrando la parte inferior de la vista:

ScrollView y HorizontalScrollView - Parte superiorScrollView y HorizontalScrollView - Parte inferior

En cualquier caso, a pesar de que la parte mostrada no incluya la barra de desplazamiento, podremos seguir moviendo el contenido a izquierda y derecha, aunque sin ver dicha barra.

Podemos hacer que el problema pase desapercibido si ocultamos ambas barras de desplazamiento, utilizando la propiedad android:scrollbars en ambas vistas.

La única forma de conseguir una vista contenedora que soporte desplazamiento en ambas direcciones mostrando correctamente las barras es crearnos una nosotros mismos. Eso queda fuera del alcance de este post, pero podéis encontrar una implementación detallada aquí: http://blog.gorges.us/2010/06/android-two-dimensional-scrollview/

Recuerda que en esta página dispones de enlaces a todos los artículos sobre Android que he publicado en La columna 80.

Una respuesta to “Especializaciones del diseño FrameLayout: ScrollView y HorizontalScrollView”

  1. wilson said

    muy bueno este post solo tengo una pregunta
    como puedo quitare el efecto de luz que indica que hay mas para mostrar?

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