La columna 80

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

Diseñando interfaces en Android: parámetros

Posted by Ángel J. Vico en 20 de septiembre de 2011

En el post anterior vimos cómo se crean diseños en XML y cómo los atributos que tenemos disponibles en XML se corresponden, de forma más o menos directa, con métodos de las clases Java que implementan las vistas. También hemos visto algunas de las propiedades más comunes de las vistas, que heredan tanto widgets como diseños.

En este post vamos a profundizar algo más en los diseños viendo, sobre todo, qué cosas tienen en común, antes de entrar en detalles particulares de cada uno de los tipos que tenemos disponibles.

Diseños

A diferencia de la mayoría de los widgets, los diseños no heredan directamente de la clase View, sino que heredan de la clase ViewGroup que, a su vez, hereda de View. De esta forma, los diseños tienen todas las propiedades de las vistas (puesto que son también vistas) y, al mismo tiempo, heredan todo lo que ofrece ViewGroup.

ViewGroup no es sólo la clase madre de los diseños, sino que aparece entre los ancestros de cualquier vista que pueda contener a otras, aunque no sean propiamente diseños. Esto conviene aclararlo. Llamamos diseños a las vistas que no sólo pueden contener otras vistas, sino que, además, se encargan de distribuirlas en la actividad, controlando su posición y dimensiones, de acuerdo a unas reglas establecidas.

Sin embargo hay otras vistas, como ListView, Gallery o DatePicker, que también contienen otras vistas. Lo que ocurre es que en estos casos, los tipos de vistas que pueden contener o la forma en que se distribuyen está muy limitada o es fija. Los diseños son bastante más flexibles.

Al igual que la clase View, la propia clase ViewGroup también proporciona algunas propiedades a los diseños. Sin embargo, no son propiedades que se utilicen con frecuencia por lo que no hablaré de ellas por el momento.

Los parámetros de los diseños

De lo que sí voy a hablar ahora es de una clase que se define dentro de la clase ViewGroup y que tiene suma importancia: ViewGroup.LayoutParams. Esta clase también define propiedades que luego se pueden usar como atributos en los archivos XML donde se definen los diseños. La diferencia radica en que estos atributos no se aplican sobre los diseños, sino sobre las vistas que contienen.

Nota

El compilador nos permite asignar valores a estos parámetros por medio de atributos en las etiquetas de las vistas como si fueran propiedades propias. Pero no debemos olvidar que siguen siendo parámetros del diseño y que es éste el que condiciona cuáles tenemos disponibles en cada caso.

Podemos tener, por ejemplo, dos Button idénticos, uno en un RelativeLayout y otro en un LinearLayout. Si el parámetro alignTop sólo está definido en el diseño RelativeLayout, el atributo correspondiente, android:layout_alignTop, sólo lo podremos usar en el <Button> que esté dentro del <RelativeLayout> y no en el otro.

El compilador recopila los valores de todos estos atributos y los encapsula en un objeto de esta clase ViewGroup.LayoutParams. Luego asigna ese objeto a la vista mediante el método setLayoutParams. Este método se define en la clase View, por lo que todas las vistas lo heredan. Posteriormente, el diseño recupera los valores de estos parámetros de las vistas y los utiliza para calcular dónde debe situar cada una de ellas y con qué dimensiones.

Todo esto nos proporciona una forma sencilla de asociar a cada vista la información necesaria para que el diseño que la contiene trabaje con ella. Y, aún más, el mecanismo de herencia de Java permite que cada diseño proporcione sus parámetros particulares haciendo que estén disponibles para todas las vistas que se incluyan en él.

Cada diseño, además de heredar de ViewGroup, también proporcionará su propia clase LayoutParams, cada una de las cuales heredará de ViewGroup.LayoutParams. Para seguir el mismo esquema, estas clases estarán definidas dentro de la clase que implementa el diseño. Por ejemplo, el diseño RelativeLayout tendrá asociada una clase RelativeLayout.LayoutParams.

Como estas clases heredan de ViewGroup.LayoutParams, dispondrán de entrada de todos los parámetros que se definen aquí. Además, las propiedades de la herencia en Java nos permiten asignar objetos de una clase hija a variables de la clase madre. Esto significa que se puede utilizar el método setLayoutParams de la clase View (disponible en todas las vistas) para asignar un objeto de una clase derivada de ViewGroup.LayoutParams como, por ejemplo, RelativeLayout.LayoutParams.

De esta forma podemos tener los parámetros particulares de cualquier diseño disponibles en todas las vistas. El compilador se encargará del resto, permitiéndonos usar los atributos XML relacionados en cada vista que incluyamos en el diseño.

Las dimensiones de una vista

Ya hemos visto que los parámetros que se definen en ViewGroup.LayoutParams van a formar parte de los parámetros de todos los diseños, pero ¿cuáles son esos parámetros?

Pues básicamente dos: ancho y alto, representados por los atributos XML android:layout_width y android:layout_height.

Nota

El prefijo layout_ es una forma de distinguir los atributos propios de las vistas de los parámetros de los diseños. Las ayudas a la codificación del editor en Eclipse nos permiten saber qué parámetros tenemos disponibles en cada vista concreta escribiendo android:layout_ y pulsando Ctrl + Espacio:

0029.01 - Asistencia contextual para archivos XML en Eclipse

El objetivo de estos dos parámetros es proporcionar a las vistas una forma de indicar cuáles deben ser sus dimensiones en relación al diseño que las contiene. Todas las vistas están obligadas a definirlos, dado que el diseño no podría visualizarlas sin esta información. Se les puede asignar un valor de tipo dimensión o una de las constantes predefinidas.

Una dimensión es un valor numérico que va acompañado de una cadena de texto que indica la unidad en la que se expresa dicho valor numérico. Por ejemplo, “20px” significa 20 píxeles y “10mm” indica 10 milímetros. Puedes ver más detalles sobre las dimensiones y las unidades que tenemos disponibles en este post. Por el momento nos basta con saber que asignar una dimensión a uno de estos atributos sirve para especificar un tamaño fijo para el ancho o el alto de la vista.

Fijar el tamaño de las vistas no suele ser muy recomendable si pretendemos que nuestra aplicación se vea bien en todo tipo de pantallas. Para estas situaciones disponemos de dos constantes que le dicen al diseño cómo debe dimensionar la vista sin dar valores exactos:

  • fill_parent: indica que queremos que el ancho o el alto de la vista se ajuste al tamaño del diseño, ocupando todo el espacio disponible en la dirección correspondiente (horizontal o vertical). Hay que tener en cuenta que esto no significa que el ancho o el alto de la vista vaya a ser exactamente el mismo que el del diseño. Si el diseño tiene definido un padding, la vista lo respetará aunque usemos fill_parent.

  • wrap_content: con esta constante indicamos que queremos que el diseño establezca para la vista un ancho o alto que sea suficiente para mostrar su contenido. Para ello el sistema calcula cuánto espacio necesita para mostrar el contenido asignado. Por ejemplo, si la vista es un TextView, tendrá que determinar qué espacio necesita para mostrar la cadena de texto asignada teniendo en cuenta la fuente que se va a utilizar y su tamaño.

Un apunte sobre fill_parent

Desde la versión 8 de la API de Android, que se corresponde con la versión 2.2 del sistema operativo, la constante fill_parent se considera obsoleta (deprecated). En su lugar hay que utilizar match_parent. Es simplemente un cambio de nombre, la funcionalidad es la misma.

Esto significa que si programamos con versiones anteriores de la API tenemos que utilizar obligatoriamente fill_parent y que si lo hacemos con la 8 o posterior deberíamos usar match_parent, aunque podemos seguir usando fill_parent hasta que una futura versión de la API la elimine.

En cualquier caso, como el valor de ambas constantes es el mismo, usar match_parent no impedirá en ningún caso el correcto funcionamiento de la aplicación en una versión de Android anterior a la 2.2.

Hay que tener en cuenta que estos parámetros no son los únicos atributos que usa el diseño para decidir las dimensiones de las vistas. Hay otros atributos, como android:minWidth y android:minHeight que también se tienen en cuenta. Esto sirve, por ejemplo, si usamos la constante wrap_content con el ancho de un EditText, que no suele tener una cadena asignada. Si no definimos un ancho mínimo con android:minWidth, el diseño mostrará una vista realmente pequeña:

EditText con WRAP_CONTENT y sin minWidthEditText con WRAP_CONTENT y con minWidth

Anidación de diseños

En realidad decidir el tamaño de cada vista es algo bastante más complejo, y no sólo porque algunos diseños añaden numerosos parámetros a tener en cuenta a la hora de determinar dónde y con qué tamaño colocar cada vista. También está el hecho de que los diseños se pueden incluir unos dentro de otros, a distintos niveles de profundidad y mezclándose con widgets en cada uno de ellos. Esto da lugar a una estructura de árbol donde la raíz es el primer diseño que se añade a la actividad y que se puede extender por numerosas ramas.

Todo esto obliga al sistema a realizar varios recorridos por este árbol, recogiendo información de cada diseño y widget y relacionándola entre sí, para acabar asignando unas coordenadas y unas dimensiones a cada una de las vistas. Algo imprescindible para poder mostrar la actividad.

Por ejemplo, un diseño también puede usar la constante wrap_content en sus propios atributos. En este caso, para poder determinar el tamaño del diseño es necesario conocer primero el de las vistas que contiene. Para ello hay que ir descendiendo en el árbol hasta llegar a vistas con tamaño fijo o determinable directamente a partir de sus propiedades. Una vez asignado el tamaño de las hojas se puede recorrer el árbol hacia arriba para asignar el de las ramas.

Pero no siempre es tan sencillo. Los atributos que sólo determinan tamaños mínimos requieren conocer qué otras vistas compiten por el espacio y qué dimensiones pretenden tener para determinar qué tamaño se asigna finalmente a cada una. Esto obliga a hacer otra pasada más por todo el árbol.

El objetivo de explicar todo esto es que comprendas que los diseños que escojas y la cantidad de niveles en que los anides pueden tener un impacto tremendo en el tiempo que se tarda en mostrar la actividad. Hay muchas situaciones en las que se puede conseguir la misma interfaz de usuario utilizando menos diseños o usando otros más óptimos. Para saber qué utilizar en cada caso hay que conocer a fondo cada diseño. Por eso dedicaré en breve unos cuantos posts a hablar sobre cada uno de los diseños disponibles en la API de Android. También publicaré más adelante otro post sobre optimización de interfaces de usuario.

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

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