Création de FormField personnalisés Flutter - SliderField et ConfortField
Introduction aux FormField personnalisés
Section titled “Introduction aux FormField personnalisés”FormField permet de créer des éléments de formulaires personnalisés qui s’intègrent parfaitement au système de validation de Flutter. Ces widgets personnalisés peuvent gérer leur propre état et validation.
FormField générique
Section titled “FormField générique”Voici la structure de base d’un FormField personnalisé :
class CustomFormField<T> extends FormField<T> { CustomFormField({ Key? key, FormFieldValidator<T>? validator, T? initialValue, AutovalidateMode? autovalidateMode, }) : super( key: key, initialValue: initialValue, validator: validator, autovalidateMode: autovalidateMode, builder: (FormFieldState<T> state) { return Column( children: [ // Votre widget personnalisé ici if (state.hasError) Text( state.errorText!, style: TextStyle(color: Colors.red), ), ], ); }, );}SliderField - Curseur avec validation
Section titled “SliderField - Curseur avec validation”Voici un exemple complet d’un SliderField intégré au système de validation :
class SliderField extends FormField<double> { SliderField({ Key? key, FormFieldValidator<double>? validator, double initialValue = 0, double min = 0, double max = 10, int? divisions, String? labelPrefix, }) : super( key: key, initialValue: initialValue, validator: validator, builder: (FormFieldState<double> state) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${labelPrefix ?? "Valeur"}: ${state.value?.toStringAsFixed(1) ?? "0"}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), SizedBox(height: 8), Slider( value: state.value ?? initialValue, min: min, max: max, divisions: divisions, label: state.value?.toStringAsFixed(1), onChanged: (value) => state.didChange(value), ), if (state.hasError) Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( state.errorText!, style: const TextStyle( color: Colors.red, fontSize: 12, ), ), ), ], ), ); }, );}ConfortField - Boutons radio avec validation
Section titled “ConfortField - Boutons radio avec validation”Exemple d’un FormField avec des boutons radio :
class ConfortField extends FormField<int> { ConfortField({ Key? key, FormFieldValidator<int>? validator, int? initialValue, }) : super( key: key, initialValue: initialValue, validator: validator ?? (value) => value != null ? null : 'Choix obligatoire', builder: (FormFieldState<int> state) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Classe de confort', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), SizedBox(height: 8), if (state.hasError) Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text( state.errorText!, style: const TextStyle( color: Colors.red, fontSize: 12, ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Radio<int>( value: 1, groupValue: state.value, onChanged: (value) => state.didChange(value), ), const Text('2nde classe') ], ), Row( children: [ Radio<int>( value: 2, groupValue: state.value, onChanged: (value) => state.didChange(value), ), const Text('1ère classe') ], ), ], ), ], ), );}DatePickerField - Sélecteur de date
Section titled “DatePickerField - Sélecteur de date”Un FormField pour la sélection de dates :
class DatePickerField extends FormField<DateTime> { DatePickerField({ Key? key, FormFieldValidator<DateTime>? validator, DateTime? initialValue, String? labelText, }) : super( key: key, initialValue: initialValue, validator: validator, builder: (FormFieldState<DateTime> state) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InkWell( onTap: () async { final date = await showDatePicker( context: state.context, initialDate: state.value ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime(2100), ); if (date != null) { state.didChange(date); } }, child: InputDecorator( decoration: InputDecoration( labelText: labelText ?? 'Date', border: OutlineInputBorder(), suffixIcon: Icon(Icons.calendar_today), errorText: state.hasError ? state.errorText : null, ), child: Text( state.value != null ? DateFormat('dd/MM/yyyy').format(state.value!) : 'Sélectionner une date', style: TextStyle( color: state.value != null ? Theme.of(state.context).textTheme.bodyLarge?.color : Theme.of(state.context).hintColor, ), ), ), ), ], ); }, );}TagsField - Sélection multiple
Section titled “TagsField - Sélection multiple”Un FormField pour la sélection de tags multiples :
class TagsField extends FormField<List<String>> { TagsField({ Key? key, FormFieldValidator<List<String>>? validator, List<String> initialValue = const [], required List<String> availableTags, String? labelText, }) : super( key: key, initialValue: initialValue, validator: validator, builder: (FormFieldState<List<String>> state) { final selectedTags = state.value ?? [];
return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (labelText != null) Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text( labelText, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ), Wrap( spacing: 8.0, runSpacing: 4.0, children: availableTags.map((tag) { final isSelected = selectedTags.contains(tag); return FilterChip( label: Text(tag), selected: isSelected, onSelected: (selected) { final newTags = List<String>.from(selectedTags); if (selected) { newTags.add(tag); } else { newTags.remove(tag); } state.didChange(newTags); }, ); }).toList(), ), if (state.hasError) Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( state.errorText!, style: const TextStyle( color: Colors.red, fontSize: 12, ), ), ), ], ); }, );}Utilisation des FormField personnalisés
Section titled “Utilisation des FormField personnalisés”Voici comment utiliser ces widgets personnalisés dans un formulaire :
class CustomFormExample extends StatelessWidget { final _formKey = GlobalKey<FormState>();
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Formulaire personnalisé')), body: Padding( padding: EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( children: [ SliderField( labelPrefix: 'Budget', min: 0, max: 1000, divisions: 20, validator: (value) { if (value == null || value < 100) { return 'Le budget doit être d\'au moins 100€'; } return null; }, ),
SizedBox(height: 20),
ConfortField( validator: (value) => value == null ? 'Sélection obligatoire' : null, ),
SizedBox(height: 20),
DatePickerField( labelText: 'Date de départ', validator: (value) { if (value == null) { return 'Date obligatoire'; } if (value.isBefore(DateTime.now())) { return 'La date doit être dans le futur'; } return null; }, ),
SizedBox(height: 20),
TagsField( labelText: 'Centres d\'intérêt', availableTags: ['Sport', 'Culture', 'Nature', 'Gastronomie', 'Histoire'], validator: (value) { if (value == null || value.isEmpty) { return 'Sélectionnez au moins un centre d\'intérêt'; } return null; }, ),
SizedBox(height: 30),
ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Formulaire valide !')), ); } }, child: Text('Valider'), style: ElevatedButton.styleFrom( minimumSize: Size(double.infinity, 48), ), ), ], ), ), ), ); }}Bonnes pratiques pour les FormField personnalisés
Section titled “Bonnes pratiques pour les FormField personnalisés”1. Gestion des états
Section titled “1. Gestion des états”Utilisez state.didChange() pour notifier les changements :
onChanged: (value) => state.didChange(value),2. Validation cohérente
Section titled “2. Validation cohérente”Implémentez une validation cohérente avec les autres widgets :
validator: (value) { if (value == null) return 'Champ obligatoire'; // Validation spécifique... return null;},3. Accessibilité
Section titled “3. Accessibilité”Ajoutez des propriétés d’accessibilité :
Semantics( label: 'Sélecteur de budget', value: '${state.value} euros', child: Slider( value: state.value ?? 0, onChanged: (value) => state.didChange(value), ),)4. Thématisation
Section titled “4. Thématisation”Respectez le thème de l’application :
Text( state.errorText!, style: TextStyle( color: Theme.of(context).errorColor, fontSize: 12, ),)Ces FormField personnalisés offrent une flexibilité maximale tout en conservant l’intégration native avec le système de validation de Flutter.