Skip to content

Thémes et stylisation Flutter

Flutter offre un système de thématisation puissant basé sur Material Design qui permet de personnaliser l’apparence de votre application de manière cohérente. Le système de thèmes permet de définir des couleurs, des polices, des formes et d’autres propriétés visuelles qui seront appliquées automatiquement à travers l’application.

ThemeData est la classe centrale pour définir l’apparence de votre application Flutter. Elle contient toutes les informations de style qui seront héritées par les widgets descendants.

MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
fontFamily: 'Roboto',
),
home: MyHomePage(),
)

Les widgets d’interface utilisateur peuvent être personnalisés graphiquement. Dans les versions récentes de Flutter, chaque composant dispose de son propre “sous-thème” :

  • ThemeData.inputDecorationTheme : InputDecorationTheme
  • ThemeData.sliderTheme : SliderThemeData
  • ThemeData.switchTheme : SwitchThemeData
  • ThemeData.checkboxTheme : CheckboxThemeData
  • ThemeData.radioTheme : RadioThemeData
  • ThemeData.popupMenuTheme : PopupMenuThemeData
  • ThemeData.toggleableActiveColor : Color
  • ThemeData.unselectedWidgetColor : Color

Material Design propose une palette de MaterialColors avec plusieurs nuances pour chaque couleur :

// Couleurs de base
Colors.cyan
Colors.cyan.shade100
Colors.cyan.shade500
Colors.cyan.shade900
// Couleurs d'accent
Colors.cyanAccent
Colors.cyanAccent.shade100
Container(
color: Colors.blue.shade300,
child: Text(
'Texte avec couleur de fond',
style: TextStyle(color: Colors.white),
),
)

Référence complète : Material Design Colors

Flutter prend en charge les thèmes sombres et clairs avec une transition animée automatique.

MaterialApp(
title: 'Mon App',
theme: ThemeData.light(), // Thème clair
darkTheme: ThemeData.dark(), // Thème sombre
themeMode: ThemeMode.system, // Suit les préférences système
home: MyHomePage(),
)
  • theme : Thème principal (généralement clair)
  • darkTheme : Thème sombre
  • highContrastTheme : Thème à fort contraste
  • highContrastDarkTheme : Thème sombre à fort contraste
  • themeMode : Mode de thème (system, light, dark)
ThemeData customLightTheme = ThemeData(
primarySwatch: Colors.indigo,
brightness: Brightness.light,
fontFamily: 'Roboto',
textTheme: TextTheme(
headline1: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
bodyText1: TextStyle(fontSize: 16),
),
);
ThemeData customDarkTheme = ThemeData(
primarySwatch: Colors.indigo,
brightness: Brightness.dark,
fontFamily: 'Roboto',
textTheme: TextTheme(
headline1: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
bodyText1: TextStyle(fontSize: 16),
),
);

Depuis Flutter 3, il est possible d’enrichir ThemeData avec vos propres classes de thématisation personnalisées.

Vous pouvez ajouter des extensions personnalisées à votre thème :

ThemeData.light().copyWith(
extensions: <ThemeExtension<dynamic>>{
MyThemeExtension(),
},
)

Vous pouvez ajouter plusieurs extensions selon vos besoins.

Chaque extension X doit étendre ThemeExtension<X> et override deux méthodes obligatoires : copyWith et lerp.

class MyThemeExtension extends ThemeExtension<MyThemeExtension> {
final Color customColor;
final double customSpacing;
const MyThemeExtension({
required this.customColor,
required this.customSpacing,
});
@override
ThemeExtension<MyThemeExtension> copyWith({
Color? customColor,
double? customSpacing,
}) {
return MyThemeExtension(
customColor: customColor ?? this.customColor,
customSpacing: customSpacing ?? this.customSpacing,
);
}
@override
ThemeExtension<MyThemeExtension> lerp(
ThemeExtension<MyThemeExtension>? other,
double t,
) {
if (other is! MyThemeExtension) {
return this;
}
return MyThemeExtension(
customColor: Color.lerp(customColor, other.customColor, t) ?? customColor,
customSpacing: lerpDouble(customSpacing, other.customSpacing, t) ?? customSpacing,
);
}
}

La méthode copyWith permet de créer des copies modifiées de l’extension :

@override
ThemeExtension<MyThemeExtension> copyWith({
Color? newCustomColor,
}) => MyThemeExtension(
customColor: newCustomColor ?? customColor,
);

La méthode lerp permet de calculer des interpolations entre 2 valeurs afin d’animer une transition :

@override
ThemeExtension<MyThemeExtension> lerp(
ThemeExtension<MyThemeExtension>? other,
double t,
) {
if (other is! MyThemeExtension) {
return this;
}
return MyThemeExtension(
customColor: Color.lerp(customColor, other.customColor, t) ?? customColor,
);
}

Pour utiliser votre extension personnalisée dans un widget :

final MyThemeExtension myThemeExtension =
Theme.of(context).extension<MyThemeExtension>()!;
Container(
color: myThemeExtension.customColor,
padding: EdgeInsets.all(myThemeExtension.customSpacing),
child: Text('Contenu stylisé'),
)

Chaque widget peut être stylisé individuellement :

Text(
'Mon texte',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
)

Récupérer les styles depuis le thème de l’application :

Text(
'Mon texte',
style: Theme.of(context).textTheme.headline6,
)
Container(
color: Theme.of(context).brightness == Brightness.dark
? Colors.grey[800]
: Colors.grey[200],
child: Text(
'Contenu adaptatif',
style: Theme.of(context).textTheme.bodyText1,
),
)

Exemple complet d’application avec thèmes

Section titled “Exemple complet d’application avec thèmes”
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App avec thèmes',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
fontFamily: 'Roboto',
extensions: <ThemeExtension<dynamic>>{
MyThemeExtension(
customColor: Colors.blue.shade100,
customSpacing: 16.0,
),
},
),
darkTheme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
fontFamily: 'Roboto',
extensions: <ThemeExtension<dynamic>>{
MyThemeExtension(
customColor: Colors.blue.shade900,
customSpacing: 16.0,
),
},
),
themeMode: ThemeMode.system,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final myTheme = Theme.of(context).extension<MyThemeExtension>()!;
return Scaffold(
appBar: AppBar(
title: Text('Exemple de thème'),
),
body: Container(
color: myTheme.customColor,
padding: EdgeInsets.all(myTheme.customSpacing),
child: Column(
children: [
Text(
'Titre principal',
style: Theme.of(context).textTheme.headline4,
),
SizedBox(height: myTheme.customSpacing),
Text(
'Texte de contenu avec style personnalisé',
style: Theme.of(context).textTheme.bodyText1,
),
],
),
),
);
}
}
  1. Centralisez vos thèmes : Créez des fichiers dédiés pour vos définitions de thèmes
  2. Utilisez des extensions : Pour des propriétés personnalisées spécifiques à votre app
  3. Respectez Material Design : Restez cohérent avec les principes de design de Google
  4. Testez les deux modes : Vérifiez que votre app fonctionne bien en mode clair et sombre
  1. Utilisez ColorScheme : Pour une palette cohérente
  2. Prévoyez l’accessibilité : Testez les contrastes pour les utilisateurs malvoyants
  3. Adaptez-vous au système : Respectez les préférences utilisateur
  1. Évitez les recréations : Réutilisez les instances de ThemeData
  2. Utilisez const : Pour les widgets statiques
  3. Optimisez les transitions : Les méthodes lerp doivent être efficaces