Flutter a transformé le paysage du développement mobile en permettant aux développeurs de créer de superbes applications compilées de manière native pour les mobiles, le Web et les ordinateurs de bureau à partir d'une seule base de code. Avec sa riche bibliothèque de widgets, son cadre d'interface utilisateur expressif et la légendaire fonctionnalité de rechargement à chaud, Flutter permet aux équipes d'itérer rapidement et de fournir des interfaces utilisateur raffinées en un temps record.
Dans ce guide complet, nous explorerons les concepts de base qui font de Flutter un choix exceptionnel pour le développement rapide d'interface utilisateur mobile, de la compréhension de l'arborescence des widgets à la mise en œuvre d'animations complexes et de mises en page réactives.
Comprendre l'arborescence des widgets Flutter
Tout dans Flutter est un widget. L'ensemble de l'application est construit comme une arborescence de widgets, où chaque widget décrit une partie de l'interface utilisateur. Cette approche déclarative signifie que vous décrivez à quoi devrait ressembler l'interface utilisateur pour un état donné, et Flutter se charge de mettre à jour le rendu lorsque l'état change.
L'arborescence des widgets est hiérarchique. Au sommet se trouve votreMaterialAppouCupertinoApp, qui fournit un échafaudage de thème et de navigation. En dessous, vous composez votre interface utilisateur à partir de widgets plus petits et réutilisables. Comprendre cette hiérarchie est fondamental pour créer des applications Flutter efficaces.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Flutter App',
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}Chaque widget de l'arborescence possède unElementcorrespondant qui représente sa position dans l'arborescence et unRenderObjectqui gère la mise en page et la peinture. Le pipeline de rendu de Flutter compare efficacement l'arborescence des widgets pour déterminer les mises à jour minimales, gardant ainsi votre interface utilisateur performante même avec des reconstructions fréquentes.
Widgets sans état et avec état
Flutter fournit deux types de widgets fondamentaux qui répondent à des objectifs différents dans l'architecture de votre application.
StatelessWidget
UnStatelessWidgetest immuable. Une fois construit, il ne peut pas changer son état interne. Ces widgets sont idéaux pour les éléments d'interface utilisateur qui dépendent uniquement de la configuration qui leur est transmise et des widgets hérités au-dessus d'eux dans l'arborescence. Ils sont légers, faciles à tester et devraient constituer votre choix par défaut.
class UserCard extends StatelessWidget {
final String name;
final String email;
final String avatarUrl;
const UserCard({
super.key,
required this.name,
required this.email,
required this.avatarUrl,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(avatarUrl),
),
title: Text(name),
subtitle: Text(email),
trailing: const Icon(Icons.arrow_forward_ios),
),
);
}
}StatefulWidget
UnStatefulWidgetmaintient un état mutable qui peut changer pendant la durée de vie du widget. Lorsque l'état change viasetState(), Flutter reconstruit le widget pour refléter le nouvel état. Utilisez des widgets avec état pour les éléments interactifs tels que les formulaires, les bascules et les composants animés.
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $_count', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}Le système de mise en page : lignes, colonnes, piles et Flex
Le système de mise en page de Flutter repose sur un ensemble puissant de widgets flexibles qui vous donnent un contrôle précis sur la façon dont les éléments sont disposés à l'écran.
Rangée et colonne
Rowdispose les enfants horizontalement, tandis queColumnles dispose verticalement. Les deux prennent en charge l'alignement le long de l'axe principal et de l'axe transversal et peuvent répartir l'espace entre les enfants à l'aide des widgetsExpandedetFlexible.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 2,
child: Text('Title', style: Theme.of(context).textTheme.titleLarge),
),
Expanded(
flex: 1,
child: ElevatedButton(
onPressed: () {},
child: const Text('Action'),
),
),
],
)Stack
Stackvous permet de superposer des widgets les uns sur les autres. Ceci est inestimable pour créer des compositions visuelles complexes telles que des superpositions d’images, des badges et des éléments d’action flottants. UtilisezPositionedpour placer les enfants à des emplacements spécifiques de la pile.
Stack(
children: [
Image.asset('assets/background.jpg', fit: BoxFit.cover),
Positioned(
bottom: 16,
left: 16,
right: 16,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'Overlay Text',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
],
)Modèles de disposition flexibles
CombinezExpanded,FlexibleetSpacerpour créer des configurations sophistiquées qui s'adaptent à l'espace disponible.Expandedoblige un enfant à remplir l'espace restant, tandis queFlexiblepermet à un enfant d'être plus petit que le facteur de flexibilité qui lui est alloué.
Conception réactive avec MediaQuery et LayoutBuilder
Créer des interfaces utilisateur réactives qui fonctionnent sur les téléphones, les tablettes et même les ordinateurs de bureau nécessite de comprendre les outils de conception réactive de Flutter.
MediaQueryfournit des informations sur l'appareil actuel, notamment la taille de l'écran, l'orientation, la densité de pixels et les préférences système. Utilisez-le pour prendre des décisions d'aménagement en fonction de l'espace disponible.
class ResponsiveLayout extends StatelessWidget {
const ResponsiveLayout({super.key});
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isTablet = screenWidth > 600;
final isDesktop = screenWidth > 1200;
return Scaffold(
body: isDesktop
? Row(
children: [
const NavigationRail(destinations: []),
Expanded(child: _buildContent(crossAxisCount: 4)),
],
)
: _buildContent(crossAxisCount: isTablet ? 3 : 2),
);
}
Widget _buildContent({required int crossAxisCount}) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: 20,
itemBuilder: (context, index) => Card(
child: Center(child: Text('Item $index')),
),
);
}
}LayoutBuilderest un autre outil puissant qui fournit les contraintes du widget parent, vous permettant de créer différentes mises en page en fonction de l'espace réellement disponible plutôt que des dimensions du plein écran. Ceci est particulièrement utile pour les composants réutilisables qui doivent s’adapter à leur conteneur.
Conception de thèmes et de matériaux 3
Le système de thèmes de Flutter vous permet de définir un langage visuel cohérent dans l'ensemble de votre application. Avec Material Design 3, vous pouvez générer une palette de couleurs complète à partir d’une seule couleur de départ.
MaterialApp(
theme: ThemeData(
colorSchemeSeed: const Color(0xFF1A73E8),
useMaterial3: true,
textTheme: const TextTheme(
headlineLarge: TextStyle(fontWeight: FontWeight.bold),
bodyLarge: TextStyle(fontSize: 16, height: 1.5),
),
cardTheme: CardTheme(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: Colors.grey.shade200),
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
),
),
)Accédez aux propriétés du thème n'importe où dans votre arborescence de widgets à l'aide deTheme.of(context). Cela garantit la cohérence visuelle et rend trivial la mise en œuvre du mode sombre en fournissant undarkThemealternatif à votreMaterialApp.
Animations et transitions
Flutter excelle dans les animations fluides et performantes. Le framework fournit des API d'animation implicites et explicites pour s'adapter à différents niveaux de complexité.
Animations implicites
Pour de simples modifications de propriétés, des animations implicites s'animent automatiquement entre les anciennes et les nouvelles valeurs. Les widgets commeAnimatedContainer,AnimatedOpacityetAnimatedPaddingnécessitent un minimum de code.
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _isExpanded ? 300 : 150,
height: _isExpanded ? 200 : 100,
decoration: BoxDecoration(
color: _isExpanded ? Colors.blue : Colors.green,
borderRadius: BorderRadius.circular(_isExpanded ? 16 : 8),
),
child: const Center(child: Text('Tap me')),
)Animations explicites
Pour plus de contrôle, utilisezAnimationControlleravec les objetsTween. Cela vous donne un contrôle précis sur le timing, le séquençage et les courbes d’animation.
class PulseAnimation extends StatefulWidget {
const PulseAnimation({super.key});
@override
State<PulseAnimation> createState() => _PulseAnimationState();
}
class _PulseAnimationState extends State<PulseAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..repeat(reverse: true);
_scaleAnimation = Tween<double>(begin: 0.95, end: 1.05).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: _scaleAnimation,
child: const FlutterLogo(size: 100),
);
}
}Transitions de page
Personnalisez les transitions de navigation à l'aide dePageRouteBuilderpour créer des transitions d'écran distinctives qui améliorent la personnalité de votre application.
Flux de travail de rechargement à chaud
Le rechargement à chaud de Flutter change la donne en termes de vitesse de développement. Lorsque vous enregistrez un fichier, Flutter injecte le code source mis à jour dans la machine virtuelle Dart en cours d'exécution, préservant ainsi l'état actuel de votre application. Cela signifie que vous pouvez modifier le remplissage d'un widget, changer une couleur ou restructurer une mise en page et voir les résultats en moins d'une seconde.
Pour maximiser l'efficacité du rechargement à chaud, suivez ces bonnes pratiques :
- Conserver les méthodes de build pures- Les méthodes de build doivent uniquement dépendent de l'état du widget et des widgets hérités. Les effets secondaires dans les méthodes de génération peuvent provoquer un comportement inattendu lors du rechargement à chaud.
- Utiliser les constructeurs const- Marquer les widgets comme
constdans la mesure du possible. Cela aide Flutter à identifier les parties de l'arborescence qui doivent être reconstruites et à améliorer les performances. - Extraire les widgets dans des classes distinctes- Plutôt que de les imbriquer profondément, extrayez les sections logiques de l'interface utilisateur dans leurs propres classes de widgets. Cela améliore la granularité du rechargement à chaud et la lisibilité du code.
- Tirer parti de DevTools- Flutter DevTools fournit un inspecteur de widgets, un profileur de performances et un explorateur de mise en page qui s'associent parfaitement au rechargement à chaud pour une itération rapide.
Conseils de performances pour l'interface utilisateur Flutter
La création d'interfaces utilisateur rapides nécessite une attention particulière à plusieurs domaines clés :
1. Utiliser les widgets const
Déclarer les widgets commeconstpermet à Flutter de les mettre en cache et d'ignorer entièrement les reconstructions. C’est l’une des optimisations les plus simples et les plus efficaces.
2. Réduire la portée de la reconstruction
Lors de l'utilisation desetState(), seuls le widget appelant et ses descendants sont reconstruits. Gardez vos widgets avec état petits et ciblés pour limiter le rayon d'action des changements d'état.
3. Utilisez ListView.builder pour les listes longues
N'utilisez jamaisListViewavec une liste statique d'enfants pour des ensembles de données volumineux. Utilisez plutôtListView.builderqui crée paresseusement les éléments au fur et à mesure qu'ils défilent, réduisant ainsi considérablement l'utilisation de la mémoire.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
subtitle: Text(items[index].description),
);
},
)4. Cacher les calculs coûteux
Utilisez le widgetRepaintBoundarypour isoler les parties de l'interface utilisateur qui changent fréquemment des sections statiques. Cela évite les repeintures inutiles du contenu stable.
5. Optimiser les images
Utilisez des images de taille appropriée et envisagez d'utilisercached_network_imagepour les images réseau. SpécifiezcacheWidthetcacheHeightsur les widgetsImagepour décoder les images à la taille d'affichage plutôt qu'à leur pleine résolution.
Création d'un exemple d'interface utilisateur complet
Rassemblons le tout avec un exemple pratique qui combine mise en page, thème et interactivité.
class ProductCard extends StatelessWidget {
final String title;
final String price;
final String imageUrl;
final VoidCallback onAddToCart;
const ProductCard({
super.key,
required this.title,
required this.price,
required this.imageUrl,
required this.onAddToCart,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(imageUrl, fit: BoxFit.cover),
),
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: theme.textTheme.titleMedium),
const SizedBox(height: 4),
Text(price, style: theme.textTheme.titleLarge?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
)),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: onAddToCart,
icon: const Icon(Icons.shopping_cart),
label: const Text('Add to Cart'),
),
),
],
),
),
],
),
);
}
}Conclusion
Flutter fournit un environnement incroyablement productif pour la création d'interfaces utilisateur mobiles. Son architecture basée sur des widgets, combinée au rechargement à chaud, à un système de thèmes riche et à de puissantes primitives de mise en page, permet aux développeurs de créer des applications belles et réactives plus rapidement que jamais. En comprenant l'arborescence des widgets, en choisissant les bons types de widgets, en tirant parti du système de mise en page et en suivant les meilleures pratiques en matière de performances, vous pouvez créer des interfaces utilisateur non seulement visuellement époustouflantes, mais également performantes et maintenables.
Que vous construisiez un prototype simple ou une application de production complexe, la boîte à outils d'interface utilisateur de Flutter s'adapte à vos besoins. Commencez par des compositions simples, exploitez le vaste catalogue de widgets et adoptez progressivement des modèles plus avancés à mesure que votre application se développe. La combinaison d'une itération rapide via le rechargement à chaud et du API expressif de Flutter fait du développement d'une interface utilisateur mobile une expérience véritablement agréable.