
De toute façon, chaque développeur Android devait travailler avec des styles. Quelqu'un se sent en confiance avec lui, quelqu'un n'a que des connaissances superficielles, ce qui ne lui permet souvent pas de résoudre le problème par lui-même.
En prévision de la sortie du thème sombre, il a été décidé de rafraîchir en mémoire toutes les informations relatives aux thèmes et styles dans les applications Android.
Ce qui sera discuté:
- Considérez les concepts de base des thèmes et des styles dans les applications Android, voyez quelles opportunités ils nous offrent;
- Créons un thème simple à l'aide des composants de matériaux et jouons avec les styles de redéfinition;
- Voyons comment fonctionne le thème sombre;
- Formuler des recommandations pour travailler avec des styles.
Commençons par les bases
Dans leur structure, les thèmes et les styles ont une structure commune:
<style name="MyStyleOrTheme"> <item name="key">value</item> </style>
Pour créer, utilisez la balise de style
. Chaque style a un nom et il stocke les paramètres de key-value
.
Tout est assez simple. Mais quelle est la différence entre le thème et le style?
La seule différence est la façon dont nous les utilisons.
Thème
Un thème est un ensemble de paramètres qui s'appliquent à l'ensemble du composant d'application, d'activité ou de vue. Il contient les couleurs de base de l'application, les styles de rendu de tous les composants de l'application et divers paramètres.
Exemple de sujet:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorOnPrimary">@color/color_on_primary</item> <item name="colorOnError">@color/color_on_error</item> <item name="textAppearanceHeadline1">@style/TextAppearance.MyTheme.Headline1</item> <item name="bottomSheetDialogTheme">@style/ThemeOverlay.MyTheme.BottomSheetDialog</item> <item name="chipStyle">@style/Widget.MaterialComponents.Chip.Action</item> <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.FilledBox</item> <item name="android:windowTranslucentStatus">true</item> </style>
Le thème a redéfini les couleurs principales de l'application ( colorPrimary
, colorSecondary
), le style du texte ( textAppearanceHeadline1
) et certains composants standard de l'application, ainsi que l'option d'une barre d'état transparente.
Pour que le style devienne un vrai sujet, il faut hériter (on parlera d'héritage un peu plus tard) de l'implémentation par défaut du sujet.
Le style
Un style est un ensemble de paramètres permettant de styliser un seul composant View.
Exemple de style pour TextInputLayout :
<style name="Widget.MyApp.CustomTextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="boxBackgroundMode">outline</item> <item name="boxStrokeColor">@color/color_primary</item> <item name="shapeAppearanceOverlay">@style/MyShapeAppearanceOverlay</item> </style>
Attribut
Un attribut est une clé de style ou de thème. Ce sont de petites briques à partir desquelles tout est construit:
colorPrimary colorSecondary colorOnError boxBackgroundMod boxStrokeColor shapeAppearanceOverlay ...
Toutes ces clés sont des attributs standard.
Nous pouvons créer nos propres attributs:
<attr name="myFavoriteColor" format="color|reference" />
L'attribut myFavoriteColor
pointera vers une couleur ou un lien vers une ressource de couleur.
Dans le format, nous pouvons spécifier des valeurs assez standard:
- couleur
- référence
- chaîne
- énumérer
- fraction
- dimension
- booléen
- drapeaux
- flotter
- entier
De par sa nature, un attribut est une interface . Il doit être implémenté dans le sujet:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="myFavoriteColor">@color/color_favorite</item> </style>
Maintenant, nous pouvons nous y référer. La structure générale de l'appel ressemble à ceci:

1 — , ; 2 — namespace ( Material Components Library); 3 — , (); 4 — .
Enfin, changeons, par exemple, la couleur du texte du champ:
<androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?attr/myFavoriteColor"/>
Grâce aux attributs, nous pouvons ajouter tout type d'abstraction qui changera à l'intérieur du sujet.
Hériter des thèmes et des styles
Comme dans la POO, nous pouvons adopter la fonctionnalité d'une implémentation existante. Il existe deux façons de procéder:
- Explicite (explicite)
- Implicite (implicitement)
Pour l'héritage explicite, nous spécifions le parent à l'aide du mot-clé parent
:
<style name="SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> </style>
Pour l'héritage implicite, nous utilisons dot-notation
par dot-notation
pour indiquer le parent:
<style name="SnackbarStyle.Green"> </style>
Il n'y a aucune différence dans le travail de ces approches.
Très souvent, nous pouvons rencontrer des styles similaires:
<style name="Widget.MyApp.Snackbar" parent="Widget.MaterialComponents.Snackbar"> </style>
Il peut sembler que le style est créé par double héritage. Ce n'est en fait pas le cas. L'héritage multiple est interdit. Dans cette définition, l'héritage explicite l'emporte toujours .
Autrement dit, un style sera créé avec le nom Widget.MyApp.Snackbar
, qui est le descendant de Widget.MaterialComponents.Snackbar
.
Superposition de thème
ThemeOverlay - ce sont des thèmes spéciaux "légers" qui vous permettent de remplacer les attributs du thème principal pour le composant View.
Nous n'irons pas loin pour un exemple, mais prenons un cas de notre application. Les concepteurs ont décidé que nous devons créer un champ de connexion standard, qui aura une couleur différente du style principal.
Avec le sujet principal, le champ de saisie ressemble à ceci:

Cela a l'air génial, mais les concepteurs insistent pour que le champ soit de style brun.
D'accord, comment pouvons-nous résoudre ce problème?
La bonne solution consiste à utiliser ThemeOverlay .
Créez ThemeOverlay
et redéfinissez la couleur principale du thème :
<style name="ThemeOverlay.MyApp.Login" parent="ThemeOverlay.MaterialComponents.TextInputEditText"> <item name="colorPrimary">@color/colorBrown</item> </style>
Ensuite, nous le spécifions à l'aide de la balise spéciale android:theme
dans notre TextInputLayout
:
<com.google.android.material.textfield.TextInputLayout android:theme="@style/ThemeOverlay.MyApp.Login" android:hint="Login" ... > <com.google.android.material.textfield.TextInputEditText ... /> </com.google.android.material.textfield.TextInputLayout>
Tout fonctionne comme nous en avons besoin.

Bien sûr, la question se pose - comment ça marche sous le capot?
Cette magie vous permet de lancer ContextThemeWrapper
. Lors de la création d'une vue dans LayoutInflater
, un contexte sera créé dans lequel le thème actuel sera pris comme base et les paramètres que nous avons spécifiés dans notre thème Overlay y seront redéfinis.
../LayoutInflater.java final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle();
De même, nous pouvons remplacer indépendamment n'importe quel paramètre de thème dans l'application.
La séquence d'application des thèmes et des styles au composant View

La priorité principale est le fichier de balisage. Si un paramètre y est défini, tous les paramètres similaires seront ignorés.
<Button android:textColor="@color/colorRed" ... />
La prochaine priorité est le style d'affichage:
<Button style=“@Widget.MyApp.ButtonStyle" ... />
Les éléments suivants utilisent des styles prédéfinis pour le composant:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“materialButtonStyle”>@Widget.MyApp.ButtonStyle</item> </style>
Si aucun paramètre n'a été trouvé, les attributs de thème sont utilisés:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“colorPrimary”>@colorPrimary</item> </style>
En général, c'est tout ce que vous devez savoir pour commencer à travailler sur des sujets. Jetons maintenant un coup d'œil à la bibliothèque de conception des composants de matériaux mise à jour.
Que les composants matériels viennent avec nous
Les composants matériels ont été introduits lors de Google I / O 2018 et remplacent la bibliothèque de support de conception.
La bibliothèque nous donne la possibilité d'utiliser des composants mis à jour de Material Design 2.0. De plus, de nombreuses options de personnalisation intéressantes y sont apparues. Tout cela vous permet d'écrire des applications lumineuses et uniques.


Voici quelques exemples d'applications dans le nouveau style: Owl , Reply , Crane .
Passons à la pratique
Pour créer un sujet, vous devez hériter du sujet de base:
Theme.MaterialComponents Theme.MaterialComponents.NoActionBar Theme.MaterialComponents.Light Theme.MaterialComponents.Light.NoActionBar Theme.MaterialComponents.Light.DarkActionBar Theme.MaterialComponents.DayNight Theme.MaterialComponents.DayNight.NoActionBar Theme.MaterialComponents.DayNight.DarkActionBar
Tous sont très similaires aux thèmes AppCompat
, mais ont des attributs et des paramètres supplémentaires.
Vous pouvez en savoir plus sur les nouveaux attributs sur material.io .
Si, pour une raison quelconque, vous ne pouvez pas passer à un nouveau sujet pour le moment, les thèmes Bridge
feront l'affaire. Ils héritent des thèmes AppCompat
et ont tous les nouveaux attributs des composants de matériau. Il vous suffit d'ajouter le suffixe Bridge
et d'utiliser toutes les fonctionnalités sans crainte:
Theme.MaterialComponents.Light.Bridge
Et voici notre sujet:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorSecondaryVariant">@color/color_secondary_variant</item> <style>
Les noms des couleurs primaires (couleurs de marque) ont changé:
colorPrimary — ( AppCompat); colorPrimaryVariant — ( colorPrimaryDark AppCompat); colorSecondary — ( colorAccent AppCompat); colorSecondaryVariant — .
Vous trouverez de plus amples informations sur les couleurs sur material.io .
J'ai déjà mentionné que le thème contient des styles standard pour chaque composant View. Par exemple, pour Snackbar
style sera appelé snackbarStyle
, pour checkbox
- checkboxStyle
, puis tout sera similaire. Un exemple mettra tout à sa place:

Créez votre propre style et appliquez-le au thème:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="snackbarStyle">@style/Widget.MyApp.SnackbarStyle</item> </style> <style name="Widget.MyApp.SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> </style>
Il est important de comprendre que lorsque vous redéfinissez un style dans un thème, il s'appliquera à toutes les vues de ce type dans l'application (Activité).
Si vous souhaitez appliquer le style à une seule vue spécifique, vous devez utiliser la balise de style
dans le fichier avec le balisage:
<com.google.android.material.button.MaterialButton style="@style/Widget.MyApp.SnackbarStyle" ... />
ShapeAppearance est l'une des innovations qui m'a vraiment impressionné. Il vous permet de changer la forme des composants directement dans le sujet!
Chaque composant View appartient à un certain groupe:
formeApparence Petit composant
formeApparence Composant moyen
formeApparence Grand composant
Comme nous pouvons le comprendre d'après le nom, en groupes de vues de différentes tailles.

Vérifiez en pratique:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="shapeAppearanceSmallComponent">@style/Widget.MyApp.SmallShapeAppearance</item> </style> <style name="Widget.MyApp.SmallShapeAppearance" parent=“ShapeAppearance.MaterialComponents.SmallComponent”> <item name="cornerFamilyTopLeft">rounded</item> <item name="cornerFamilyBottomRight">cut</item> <item name="cornerSizeTopLeft">20dp</item> <item name="cornerSizeBottomRight">15dp</item> </style>
Nous avons créé Widget.MyApp.SmallShapeAppearance
pour les "petits" composants. Nous avons arrondi le coin supérieur gauche de 20dp
et le coin inférieur droit coupé de 15dp
.
Vous avez ce résultat:

Ça a l'air intéressant. Cela fonctionnera-t-il dans la vraie vie? Le temps nous le dira.
Comme pour les styles, nous ne pouvons appliquer ShapeAppearance
qu'à un seul composant View.
Qu'y a-t-il sur un sujet sombre?
La sortie d'Android Q aura lieu très bientôt, et avec elle le thème sombre officiel nous parviendra.
L'une des fonctionnalités les plus intéressantes et spectaculaires de la nouvelle version d'Android est peut-être l'application automatique d'un thème sombre pour l'ensemble de l'application avec une seule ligne de code.
Sonne bien, essayons. Je suggère de prendre un client gitlab préféré de terrakok .
Autoriser la repeinture de l'application (désactivée par défaut):
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:forceDarkAllowed">true</item> </style>
L' android:forceDarkAllowed
est disponible avec l'API 29 (Android Q).
Nous commençons, regardez ce qui s'est passé:

Convenez que pour une ligne de code, cela semble très cool.
Bien sûr, il y a des problèmes - BottomNavigationBar
fusionne avec l'arrière-plan, le chargeur reste blanc, la sélection de code souffre et, semble-t-il, tout, du moins rien de grave ne m'a frappé.
Je suis sûr que passer moins de temps peut résoudre les principaux problèmes. Par exemple, désactiver le mode sombre automatique pour les vues individuelles (oui, il est également possible - android:forceDarkAllowed
est disponible pour View dans un fichier de balisage).
Il faut se rappeler que ce mode n'est disponible que pour les thèmes clairs, si vous en utilisez un sombre, alors un thème sombre forcé ne fonctionnera pas.
Les recommandations de travaux se trouvent dans la documentation et sur material.io .
Et si nous voulons tout faire par nous-mêmes?
Peu importe la facilité d'utilisation d'un thème sombre forcé, ce mode est dépourvu de flexibilité. En fait, tout fonctionne selon des règles prédéfinies qui peuvent ne pas nous convenir et, surtout, le client. Je pense qu'une telle décision peut être considérée comme temporaire, jusqu'à ce que nous fassions notre mise en œuvre d'un sujet sombre.
Dans l'API 8 (Froyo), le qualificatif -night
été ajouté, qui est utilisé à ce jour pour appliquer un thème sombre. Il vous permet d'appliquer automatiquement le thème souhaité en fonction de l'heure de la journée.
Dans les thèmes DayNight
, une telle implémentation est déjà utilisée, il nous suffit d'en hériter.
Essayons d'écrire le nôtre:
../values/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents.Light"/> <style name="Theme.MyApp.Main" parent="Theme.DayNight.Base> <!-- ... --> </style> ../values-night/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents"/>
Dans la ressource habituelle pour le thème ( values/themes.xml
) nous values/themes.xml
du thème light, dans la "nuit" ( values-night/themes.xml
) nous values-night/themes.xml
du thème dark.
C’est tout. Nous avons obtenu une implémentation de bibliothèque d'un thème sombre. Maintenant, nous devons soutenir les ressources pour deux sujets.
Pour basculer entre les thèmes pendant que l'application est en cours d'exécution, vous pouvez utiliser AppCompatDelegate.setDefaultNightMode
, qui prend les paramètres suivants:
MODE_NIGHT_NO
- Thème lumineux;MODE_NIGHT_YES
- Thème sombre;MODE_NIGHT_AUTO_BATTERY
- Mode automatique. Le thème sombre s'active si le mode d'économie d'énergie est activé;MODE_NIGHT_FOLLOW_SYSTEM
- Mode basé sur les paramètres système.
Que devons-nous considérer lorsque nous travaillons avec des thèmes et des styles?
Comme je l'ai noté, Google a officiellement commencé à forcer un sujet sombre. Je suis sûr que de nombreux clients ont commencé à recevoir des questions - "Pouvons-nous ajouter un thème sombre?". C'est bien si vous faites tout dès le début et il est facile pour vous de changer les couleurs claires en foncées, tout en recevant une application complètement repeinte.
Malheureusement, ce n'est pas toujours le cas. Il existe de vieilles applications qui nécessitent des efforts considérables pour apporter les modifications nécessaires.
Essayons de formuler des recommandations pour travailler ensemble avec des styles:
1. Le sélecteur de couleurs
Je pense que chaque développeur est confronté à une situation où une couleur étrange apparaît dans la nouvelle mise en page qui n'est pas encore définie dans la palette d'application. Que faire dans ce cas?
La bonne réponse est de parler au designer et d'essayer de développer une palette de couleurs. Il existe maintenant de nombreux programmes (Zeplin, Sketch, etc.) qui vous permettent de rendre les couleurs primaires, puis de les réutiliser.
Plus tôt vous le ferez, moins vous aurez de maux de tête à l'avenir.
2. Épeler les couleurs par leurs noms propres
Dans chaque application, il existe une couleur qui possède de nombreuses options de luminosité. Vous pouvez commencer à leur inventer des noms:
<color name="green_tiny">...</color> <color name="green_light">...</color> <color name="green_dark">...</color>
D'accord, ça n'a pas l'air très bien. La question se pose immédiatement - quelle couleur est plus claire que tiny
ou light
? Et si nous avons une dizaine d'options?
Il est préférable de s'en tenir au concept de Google et d'ajouter la luminosité appropriée aux noms de couleur (Google appelle cette option de couleur - colorVariant
):
<color name="material_green_300">...</color> <color name="material_green_700">...</color> <color name="material_green_900">...</color>
Avec cette approche, nous pouvons avoir un certain nombre d'options de luminosité d'une seule couleur et nous n'avons pas à proposer de noms spécifiques, ce qui est vraiment difficile.
3. Abstraire d'une couleur spécifique si elle change dans différents sujets
Puisque nous écrivons une application dans laquelle il y aura au moins deux sujets, nous ne pouvons pas nous permettre de faire référence à une couleur spécifique si elle est implémentée dans les thèmes de différentes manières.
Regardons un exemple:

Nous voyons que dans un thème clair, par exemple, la barre d'outils est colorée en violet, et dans le noir, elle est gris foncé. Comment pourrions-nous implémenter ce comportement en utilisant uniquement les capacités des thèmes?
Tout est assez simple - nous allons créer un attribut et l'implémenter dans des thèmes clairs et sombres avec la couleur appropriée, comme décrit précédemment.
Google recommande d'associer les noms d'attributs à la sémantique d'utilisation.
4. N'ayez pas peur de créer des fichiers de ressources
Lorsque de nombreux styles, thèmes et attributs différents sont saisis dans styles.xml
, il devient difficile à maintenir.
Il est préférable de tout regrouper dans des fichiers séparés:
themes.xml — Theme & ThemeOverlay styles.xml — Widget styles type.xml — TextAppearance, text size etc shape.xml — ShapeAppearance motion.xml — Animations styles system_ui.xml — Booleans, colors for UI control //may be other files
Une règle aussi simple évitera les fichiers divins et, par conséquent, il sera plus facile de maintenir les styles.
5. Réutiliser au maximum
Que ferons-nous si nous voulons redéfinir un attribut qui n'est disponible qu'à partir d'une version spécifique de l'API?
Nous pouvons créer deux sujets distincts:
../values/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> </style> ../values-v27/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> <name="android:windowLightNavigationBar">...</item> </style>
Avons-nous maintenant un thème avec tous les paramètres pour chaque version de l'API? Bien sûr que non! Nous allons créer un sujet de base où les attributs de base disponibles pour toutes les versions de l'API seront déterminés et hérités de celui-ci dans la version souhaitée de l'API:
../values/themes.xml <style name="Theme.MyApp.Base" parent=”Theme.MaterialComponents.DayNight.NoActionBar”> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base”/> ../values-v27/themes.xml <style name="Theme.MyApp.Base.V27" parent="Theme.MyApp.Base"> <name="android:windowLightNavigationBar">...</item> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base.V27”/>
Par ce principe, tous les sujets de la bibliothèque standard sont créés.
6. Utilisez les ressources vectorielles et la teinte
Je pense que cela ne vaut pas la peine de dire pourquoi les ressources vectorielles sont bonnes. Tout le monde le sait déjà (juste au cas où, un lien vers la documentation ). Eh bien, la teinture nous aidera à les colorer dans des couleurs de thème.
Vous pouvez voir ce qu'est la teinture et comment l'utiliser avec cet exemple .
7.? Android: attr / ... vs? Attr / ...
Lors de l'accès aux ressources, nous avons la possibilité d'utiliser à la fois les attributs système et les attributs de la bibliothèque des composants de matériaux. Il est important de comprendre que certains attributs n'existent qu'avec une version spécifique de l'API. Comme nous le savons tous, l'accès à une ressource inexistante entraîne un plantage (peluches, bien sûr, nous dira si quelque chose ne va pas, mais vous ne devriez pas toujours vous y fier)
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
Dans le premier cas, nous accédons à la ressource système, comme indiqué par android
. Dans le second cas, à un attribut de la bibliothèque où la compatibilité descendante est implémentée.
Il est préférable de toujours utiliser la deuxième option.
8. Toujours spécifier le parent pour le style
Il peut y avoir des paramètres dans le style parent, sans lesquels le composant ne sera pas rendu correctement, vous devez donc toujours spécifier le parent.
<style name="Widget.MyApp.LoginInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="errorTextColor">@color/colorError</item> </style>
9. Thème, style ou ...?
Lors de la création de vos propres thèmes et styles, ce sera génial si vous spécifiez un préfixe qui indique de quel type de style il s'agit et pour quoi il est défini. Une telle dénomination facilitera la structure et l'extension des styles.
<style name="Theme.MyApp.Main" parent=”...”/> <style name="Widget.MyApp.LoginInputLayout" parent="..."/> <style name="Widget.MyApp.LoginInputLayout.Brown"/> <style name="ThemeOverlay.MyApp.Login" parent=”...”/>
10. Utilisez TextAppearance
Ce sera un bon ton pour étendre les styles de base du texte et les utiliser partout.
Material Design: Typography , Typography Theming .
Conclusion
, — , . - . Material Components. . Sketch — Material Theme Editor . . , .
Material Components GitHub — Modular and customizable Material Design UI components for Android . . , — sample, .
Liens utiles: