Conception d'un indicateur de rafraîchissement liquide personnalisé avec Flutter

L'indicateur de rafraîchissement "Liquid Pull To Refresh" apporte une dimension organique et fluide aux interfaces Flutter. Contrairement aux indicateurs standards, il utilise des simulations physiques de fluides et des animations de chemins personnalisés pour transformer un geste de balayage banal en une expérience utilisateur interactive.

Architecture et gestion des états

Le fonctionnement d'un tel composant repose sur une machine à états rigoureuse. Le cycle de vie du rafraîchissement est segmenté en plusieurs phases distinctes afin de coordonner les animations visuelles avec les données de défilement.

enum FluidRefreshStatus {
  idle,      // Repos
  dragging,  // Traction en cours
  armed,    // Seuil de déclenchement atteint
  snapping,  // Transition vers la position de chargement
  loading,   // Traitement asynchrone (onRefresh)
  finished,  // Animation de fin
  aborted    // Annulation par l'utilisateur
}

Au sein de la classe d'état, plusieurs instances de AnimationController sont nécessaires pour piloter indépendamment l'oscillation de la vague, la rotation de l'icône de progression et l'opacité du conteneur.

Modélisation de l'effet liquide via CustomClipper

L'aspect visuel de "liquide" est généré par un découpage dynamique de la surface. On utilise la classe CustomClipper<Path> pour dessiner une courbe de Bézier dont la tension et la hauteur varient en fonction de la profondeur du glissement (offset).

class WaveShapeClipper extends CustomClipper<Path> {
  final double fluidHeight;
  final double curveIntensity;

  WaveShapeClipper({required this.fluidHeight, required this.curveIntensity});

  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(0.0, fluidHeight);
    
    // Création de la courbure parabolique
    final controlPoint = Offset(size.width / 2, fluidHeight + curveIntensity);
    final endPoint = Offset(size.width, fluidHeight);
    
    path.quadraticBezierTo(controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy);
    path.lineTo(size.width, 0.0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(WaveShapeClipper oldClipper) => 
      oldClipper.fluidHeight != fluidHeight || oldClipper.curveIntensity != curveIntensity;
}

Intégration pratique dans un projet

Pour implémenter ce composant, il suffit d'envelopper une vue défilable (comme ListView ou GridView). Le paramètre onRefresh doit retourner un Future pour signaler la fin du chargement.

LiquidPullToRefresh(
  color: Colors.blueAccent,
  backgroundColor: Colors.white,
  height: 100,
  onRefresh: () async {
    await Future.delayed(Duration(seconds: 3));
    // Logique de mise à jour des données
  },
  child: ListView.separated(
    itemCount: 15,
    separatorBuilder: (context, index) => Divider(),
    itemBuilder: (context, index) => ListTile(
      title: Text("Entrée de donnée n°$index"),
    ),
  ),
)

Guide de réécriture : Créer son propre système

Pour recréer ce comportement de zéro, il est essentiel de capturer les événements de défilement sans interférer avec le comportement natif de la liste.

1. Capture des notifications de défilement

Utilisez un NotificationListener de type ScrollNotification pour calculer le ratio d'étirement.

NotificationListener<ScrollNotification>(
  onNotification: (notification) {
    if (notification is ScrollUpdateNotification) {
      // Calculer l'extension au-delà des limites (overscroll)
      double dragOffset = notification.metrics.pixels;
      if (dragOffset < 0) {
        _updateAnimationValue(dragOffset.abs());
      }
    }
    return false;
  },
  child: myListView,
)

2. Synchronisation de l'animation de rebond

Une fois que l'utilisateur relâche la pression, si le seuil est atteint, le composant doit passer en mode snapping. On utilise souvent CurvedAnimation avec Curves.elasticOut pour simuler la tension de la surface du liquide qui reprend sa place.

Paramètres de personnalisation avancés

La flexibilité du composant permet d'ajuster l'esthétique selon la charte graphique de l'application :

  • springAnimationDurationInMilliseconds : Contrôle la nervosité du retour élastique.
  • borderWidth : Définit l'épaisseur de l'anneau de progression central.
  • showChildOpacityTransition : Active ou désactive le fondu de la liste pendant l'étirement.

En combinant des Path dynamiques et des contrôleurs de timing précis, Flutter permet de transformer des cmoposants UI utilitaires en éléments de design sophistiqués.

Étiquettes: Flutter Dart Animation CustomClipper UI-Design

Publié le 29 juin à 23h05