Introduction aux styles et aux thèmes

Cyril Mottier

Cet article vous présente une particularité assez plaisante d'Android vis-à-vis des autres systèmes d'exploitation mobiles : la possibilité de créer des interfaces graphiques à l'aide de simples fichiers XML.

N'hésitez pas à nous faire part de votre avis sur cet article : Commentez ♪

- Titre : Introduction aux styles et aux thèmes

- Auteur : Cyril Mottier

- Parution : 27 janvier 2011

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2011 Cyril Mottier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Introduction

- Appliquer un style à une balise

- Notion d'héritage

- Les thèmes

- Les attributs de style

- Etendre des thèmes Android

- Remerciements

Introduction

De toutes les particularités d'Android vis-à-vis des autres systèmes d'exploitation mobiles, une me plait tout particulièrement : la possibilité de créer des interfaces graphiques à l'aide de simples fichiers XML. Cette particularité favorise grandement la clarté du code source des applications. En effet, séparer la description de l'interface graphique (layout XML) de la logique applicative (code Java) est en gage d'extensibilité.

Etant très attaché aux technologies du Web (XHTML, JavaScript, CSS, etc.), j'ai souvent tendance à comparer la création d'applications au développement d'un site Internet.

Ainsi :

- les layouts s'apparentent au contenu XHTML ;

- le code applicatif à la logique JavaScript.

Les aficionados du développement Web auront probablement remarqué qu'une composante est absente du listing précédent : la présentation, généralement décrite via une feuille de style CSS. L'utilisation du CSS permet ainsi de définir un ensemble de styles applicables aux différentes balises de l'interface graphique. Android inclut un système proche des CSS : les styles et les thèmes.

Souvent sous-utilisés, les styles et thèmes Android permettent de grandement factoriser les styles et donc de conserver une cohérence à travers une application. C'est une des raisons pour lesquelles GreenDroid utilise largement ces possibilités. Nous allons donc en faire une rapide présentation dans les lignes qui suivent.

Note :
L'ensemble des démos de cet article sont disponibles dans cette archive.
Cette dernière ne contient qu'un unique projet avec lequel il vous faudra "jouer" pour appliquer telle ou telle méthode de style. N'hésitez pas à laisser un commentaire si vous n'arrivez vraiment pas à obtenir un résultat fonctionnel !

Appliquer un style à une balise

Prenons le code suivant :

<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:text="Bonjour !!!" />

Ce code n'a rien de compliqué et est même plutôt "classique". Par défaut Android oblige le développeur à définir une hauteur et une largeur pour chacune des vues. C'est ce qui nous oblige à souvent ajouter du code du style android:layout_[width|height]="[wrap_content|fill_parent]". Il faut avouer que ça peut devenir assez rébarbatif. Il est néanmoins possible de réduire le code de la façon suivante :

<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/FillParent"
android:gravity="center"
android:text="Bonjour !!!" />

Note : L'attribut XML style n'appartenant pas au namespace android, il n'est pas nécessaire de le préfixer par android:.

Pour ce faire, il suffit de créer un style FillParent dans les styles du projet :

<style name="FillParent">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">fill_parent</item>
</style>

Note : On utilise généralement le fichier res/values/styles.xml pour définir l'ensemble des styles d'un projet. Sachez néanmoins que cela n'a rien d'obligatoire. La seule nécessité est de définir le style dans la balise <resources /> dans un fichier présent dans res/values.

Notion d'héritage

Contrairement aux feuilles de style CSS, les styles Android ne fonctionnent pas en cascade. Ainsi une balise <TextView> contenue dans un <LinearLayout> disposant d'un style @style/MyStyle n'hérite aucunement des propriétés de son parent. Il est néanmoins possible de faire hériter un style d'un autre style. Si nous souhaitons maintenant créer un style forçant la vue à prendre la taille de son parent et à centrer son contenu nous pouvons définir :

<style name="FillParent.Centered" parent="@style/FillParent">
<item name="android:gravity">center</item>
</style>

Il est également possible de simplifier cette déclaration de la façon suivante :

<style name="FillParent.Centered">
<item name="android:gravity">center</item>
</style>

Lorsque le nom d'un style est du format <Part1>.<Part2>, Android considère automatiquement que le parent est le style de nom @style/Part1.

En utilisant ce nouveau style, il est possible de simplifier notre layout exemple :

<TextView
style="@style/FillParent.Centered"
android:text="Bonjour !!!" />

Les thèmes

Les thèmes Android ne sont rien d'autre que des styles s'appliquant à l'ensemble des balises de vos layouts. Pour contrer le problème de android:layout_[width|height] obligatoires, on peut donc, par exemple, imaginer un thème initialisant l'ensemble de ces valeurs à fill_parent. On commence donc par créer un style nommé Theme.Custom héritant du thème Android Theme.Black. Traditionnellement, bien qu'un style soit un thème, on le place dans un fichier nommé res/values/themes.xml.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Custom" parent="@android:style/Theme.Black">
<item name="android:layout_height">fill_parent</item>
<item name="android:layout_width">fill_parent</item>
</style>
</resources>

On applique ensuite ce thème à l'ensemble de l'application en ajoutant l'attribut XML android:theme="@style/Theme.Custom" à la balise <application /> de notre AndroidManifest.xml. Le code du layout est donc de la forme suivante :

<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center"
android:text="Bonjour !" />

Les attributs de style

Utilisés tels quels, les thèmes n'ont que peu d'intérêt. En effet, appliquer une valeur par défaut à l'ensemble des balises de votre application est quelque peu hasardeux et inutile. Les thèmes prennent tout leur sens lorsqu'ils sont utilisés en conjonction avec les attributs de style.

Un attribut de style peut-être utilisé comme "indirection de style". Plutôt que de longs discours, appliquons un attribut de style à notre <TextView />. On commence par créer un nouvel attribut nommé myTextViewAttr dans le fichier res/values/attrs.xml :

<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="myTextViewAttr" />
</resources>

Il ne nous reste plus maintenant qu'à appliquer ce style au layout voulu :

<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
style="?attr/myTextViewAttr"
android:text="Bonjour !" />

La <TextView /> précédente dispose de l'attribut de style ?attr/myTextViewAttr. On notera bien la présence d'un caractère "?" en lieu et place du traditionnel "@". L'utilisation du point d'interrogation permet d'informer le système que la ressource de style recherchée n'est pas myTextViewAttr (nous aurions alors écrit @style/myTextViewAttr) mais un style "pointé" par l'attribut myTextViewAttr.

Si on lance notre application dans l'état, nous obtenons une erreur affirmant qu'aucun des attributs android:layout_[height|width] n'a été initialisé. Ce résultat est tout à fait logique car le style qui se cache derrière l'attribut myTextViewAttr n'a pas été défini. Pour le définir, il suffit de l'initialiser dans le thème de notre application !

<style name="Theme.Custom_2" parent="@android:style/Theme.Black">
<item name="myTextViewAttr">@style/FillParent.Centered</item>
</style>

L'application est maintenant parfaitement fonctionnelle et applique le style @style/FillParent.Centered à l'ensemble des balises disposant de l'attribut de style ?attr/myTextViewAttr. Simple et puissant n'est-ce pas ?

Etendre des thèmes Android

L'ensemble des fonctionnalités décrites dans cet article sont accessibles au développeur aussi bien pour créer ses propres styles que pour étendre et modifier les thèmes existants d'Android. Ainsi, lorsque vous souhaitez développer une application à dominante "noir sur blanc", il suffit de créer un thème héritant de @android:style/Theme.Light et de modifier les attributs à votre convenance. Voici un exemple de thème Android supprimant la fameuse ombre en haut de l'écran et modifiant le style par défaut de la titlebar :

<style name="Theme.Custom_3" parent="@style/Theme.Custom_2">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowTitleStyle">@style/WindowTitle</item>
<item name="android:windowTitleBackgroundStyle">@style/WindowTitleBackground</item>
</style>

Les styles associés sont définis de la façon suivante :

<style name="WindowTitleBackground">
<item name="android:background">@drawable/title_bar</item>
</style>

<style name="WindowTitle" parent="@android:style/WindowTitle">
<item name="android:textAppearance">@style/TextAppearance.WindowTitle</item>
</style>

<style name="TextAppearance.WindowTitle" parent="@android:style/TextAppearance.WindowTitle">
<item name="android:textStyle">bold|italic</item>
</style>

Le résultat est quelque peu coloré !

A mon grand regret, la documentation sur les différents attributs de style d'Android est assez pauvre. Jetez donc un oeil à la documentation de R.attr et n'hésitez surtout pas à regarder le code source de la plateforme pour connaître l'utilité de tel ou tel attribut. Stylez/Thèmez bien !

Remerciements

Un grand merci à ClaudeLELOUP pour sa relecture.