Android: entendiendo los Themes y los Styles

[Fuente: http://brainflush.wordpress.com/2009/03/15/understanding-android-themes-and-styles/]

Que son los Android styles?

Un style en Android es una colección de pares atributo/valor aplicados a una View, a un Activity o a la aplicación entera (es decir, todas las Activities de tu aplicación). Los styles para las Activities se les llama themes, pero no nos confundamos: son sintacticamente equivalentes a los styles (porque de hecho son styles), simplemente tienen otro ambito. En este punto queremos dejar claro algo antes de seguir: sean Themes o Styles en Android no tienen nada que ver con estilos de usuario de aplicaciones como por ejemplo de Firefox. Los usuarios de Android no pueden cambiar el look de las aplicaciones de Android bajandose themes desde el Market o creando los suyos propios (todavia). Los themes de Android solo son de interes para los programadores, es una forma de hacer tus apps un poco más originales.

Cómo definimos custom styles?

Veamos una definición de theme simple  (en un fichero res/values/styles.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <style name="MyTheme" parent="android:Theme.Light">
	 <item name="android:windowNoTitle">true</item>
	 <item name="android:windowBackground">@color/translucent_blue</item>
	 <item name="android:listViewStyle">@style/MyListView</item>
 </style>

 <style name="MyListView" parent="@android:style/Widget.ListView">
	 <item name="android:listSelector">@drawable/list_selector</item>
 </style>
</resources>

Primero, declaramos un nuevo theme llamado MyTheme, el cual hereda de otro llamado Theme.Light del android namespace (especificado en el atributo parent). Esto significa que todos los estilos que nosotros no especifiquemos explicitamente en nuestro custom theme vendrán heredado del android:Theme.Light.

Además en nuestra definición, también configuramos algunos styles personalizados utilizando el elemento ‘item’. Configuramos el atributo windowNoTitle a true, de forma que todas las activities que utilizan nuestro theme no tendrán un barra de títulos.También configuramos el color de fondo de la ventana a un color personalizado llamado ‘translucent_blue’; esta es una referencia de recurso (indicado por el simbolo @) a una definición de color en values/colors.xml. También podriamos poner una referencia a un recurso drawable, o poner directamente el codigo hexadecimal de un color.Finalmente, configuramos el estilo por defecto para los widgets de tipo ListView a un estilo personalizado llamado MyListView. La definición de este estilo sigue la misma estructura que la definición de MyTheme, solo que esta vez no es un Theme , sino simplemente un estilo que solo puede ser aplicados a objetos ListView.Hereda los estilos por defecto de ListView pero reemplaza la imagen por defecto del selector por un custom drawable.

Hay dos cosas importantes a entender aqui. Primero, las dos definiciones de estilo son completamente independientes la una de la otra (otra cosa distinta es que referenciamos MyListView en MyTheme). Esto significa , que si borramos la referencia a MyListView desde MyTheme, puedo aún utilizar ese estilo aplicandolo manualmente a una declaración de ListView en un fichero de layout. Piensa en los estilos como en un conjunto de atributos  y valores que simplifican la definición de tus pantallas, asi en vez de escribir

<ListView android:listSelector="@drawable/list_selector" />

escribimos

<ListView style="@style/MyListView" />

o mejor todavia, permitimos que este estilo sea aplicado automaticamente a todos los widgets de tipo ListView configurandolo en una definición de theme (como lo hemos hecho arriba). Observese que no ponemos ‘android:’ , el espacio de nombres de android, aqui, esto es asi , el atributo ‘style’ no está definido en el espacio de nombres de ‘android’. Ta dará error si intentas poner un estilo con  ‘android:style’, there is no such attribute.

De esa forma nucna tenemos que tocar la definición de ListView nunca más, podemos cambiar los estilos de estos widgets desde un archivo, la hoja de estilos. Esto ayuda a dejar limpio el código de la definición de la estructura de los layouts, mientras la definición del look de los objetos esta toda incluida en la hoja de estilos. Y además , lo más importante, las definiciones de tus estilos pueden ser reutilizados, lo que es particularmente útil si los estilos son compartidos entre varias vistas.

La otra cosa importante a entender es que los estilos no tienen ningun tipo de semántica. Cuando creamos un estilo llamado ‘MyListView’ que hereda de Widget.ListView, entonces lo que la intuición nos dice es que estamos creando un estilo que solo se aplica a objetos ListView. Esto no es del todo cierto. No hay mecanismo que chequee si de hecho estamos aplicando ese estilo a ListView o incluso prevenirlo de aplicarselo a un objeto de un tipo completamente distinto. Es decir lo que sucede es que Widget.ListView define algunos atributos de estilo que sólo tienen sentido cuando son aplicados a objetos ListView (como por ejemplo un listDivider), esto no nos impide , sin embargo, crear un estilo que localmente tiene sentido para cualquier tipo de widget (de atributos que están definidos a nivel de la clase View por ejemplo). En resumen, el programador cuando define un estilo debe saber si los atributos que cambia tienen sentido al aplicarselo a los objetos que quiere cambiar de look. Android no chequea estas cosas. Lo más probable es que el no haga nada si el atributo definido no tiene sentido en el objeto aplicado.

Qué puedo personalizar de estilos , y desde donde puedo heredar?

Para continuar sabiendo de estilos, es importante conocer dos cosas:

  1. en qué objetos van a ser aplicados
  2. qué estilos vienen ya de herencia

La respuesta fácil es: Todo lo definido en  android.R.styleable puede ser utilizado en una hoja de estilos para heredar de ellos. Asi , volviendo a nuestro ejemplo de estilo de ListView, el elemento de estilo android:listSelector puede ser encontrado en android.R.styleable, mientras android:style/Widget.ListView está definido en android.R.style.Estos dos ficheros son por lo tanto donde debes consultar cuando empiezas a definir estilo , son como los libros de referencia de estilos. Generalmente podemos heredar todos los estilos por defecto primero, y entonces sobreescribir los que quieres personalizar.

Truquillos útiles

Text Appearance

Te has encontrado con la situación de estar definiendo el tamaño del texto y su color una y otra vez en tus layouts? No lo hagas asi, utilizar text appearances. Los Text appearances son estilos. Android ya define algunos por defecto que puedes personalizar (estos están definidos por supuesto en el R.style). Esto ayuda mucho para no repetir código, y te ayuda a organizarte  en alguna estructura que organize tus distintos tipos de letra por medio a agrupaciones de estilos que puedes referenciar y reutilizar. Si, por ejemplo, quieres cambiar la text appearance por defecto de tu app, simplemente define un custom text appearance en tu hoja de estilos y configurala y ponla como la hoja de estilos por defecto en tu theme:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <style name="MyTheme" parent="android:Theme.Light">
     <item name="android:textAppearance">@style/MyDefaultTextAppearance</item>
 </style>

 <style name="MyDefaultTextAppearance" parent="@android:style/TextAppearance">
     <item name="android:textSize">12sp</item>
     <item name="android:textColor">#333</item>
     <item name="android:textStyle">bold</item>
 </style>
</resources>

Es bueno que te conozcas todas las text appearances que Android ya tiene definidos, y los puedas personalizar para tu app como necesites.

Colores

Evita configurar valores de colores directamente en los layouts o los estilos. Definelos en un fichero de recursos de color , y sólo utiliza referencialos desde los layouts y estilos. Esto ayuda a separarlos del resto del código de vista y hace fácil cambiarlos a posteriori. Por ejemplo , para los colores que no son dependientes del estaod , puedes definir un fichero colors.xml en res/values:

<?xml version="1.0" encoding="UTF-8"?>
	<resources>
	 <color name="default_text_color">#FFEAEAEA</color>
	 ...
	</resources>

A menudo, sin embargo, los colores son más complejos. Si. por ejemplo, utilizamos una imagen que personalice el list selector, puede pasar que el texto de la lista sea legible cuando no está seleccionado, pero se convierte dificil o imposible de leer cuando el custom selector está dibijado (por ejemplo,  porque el color es muy similar). En ese caso, necesitas un ColorStateList, que puedes utilizar automaticamente para cambiar automáticamente colores para la vista que esta siendo aplicada, basandose en el estado de la vista. Empieza creando un nuevo fichero, por ejemplo ‘stateful_text_color.xml’ en la carpeta res/colors. El recurso raíz es ‘selector’, el cual es el que siempre utilizas cuando creas recursos que cambian con el estado de la vista:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
	 <item android:state_enabled="false" android:color="#333" />
	 <item android:state_window_focused="false" android:color="#CCC" />
	 <item android:state_pressed="true" android:color="#FFF" />
	 <item android:state_selected="true" android:color="#FFF" />
	 <item android:color="@color/default_text_color" /> <!-- not selected -->
</selector>

Esa es la idea. Puedes configurar este recurso de color dependientes de estado como cualquier otro, es solo que es definido de otra forma.

Ten en cuenta siempre que ya hay colores definidos que vienen con Android. El más útil de ello es probablemente el android:color/transparent, que corresponde al color  #00000000. Los primeros dos digitos hexadecimales, representan el canal alpha que es utilizado para definir la opacidad. Un valor de cero significa que no hay opacidad o lo que es lo mismo 100% de transparencia; el resto son los bits de colory no tiene relevancia, porque son invisible en este caso. Puedes entonces poner el fondo de tus vistas transparente haciendo lo siguiente:

	<style name="MyListView" parent="@android:style/Widget.ListView">
	 <item name="android:background">@android:color/transparent</item>
	</style>

Esto es todo por ahora amigos.