Dans les nombreuses idées que j’ai pour mon blog, voici une série que j’avais envie de pousser depuis un moment : “Dessine-moi un widget”. L’idée est très simple : je trouve une maquette sur internet avec un widget potentiellement intéressant, puis je l’implémente et explique la technique. Quand je dis intéressant, c’est un widget sur lequel, en voyant la maquette, tu te dis : “tiens, mais comment ferais-je ça ?”.

Pour ce premier article de la série, je vais reprendre la question qu’un ami m’avait posée lorsqu’il débutait en Flutter : “Comment faire un dégradé dans un texte ?”. Cette question permet d’effleurer des thématiques un peu avancées dans Flutter ce qui en fait, je trouve, un bon premier sujet. Dans cet article nous allons refaire l’effet du texte “ama.” de cette maquette trouvée sur dribbble.

Maquette incluant un dégradé dans un texte

Pour commencer, pour mettre un dégradé sur un Container, il existe l’attribut gradient sur l’objet BoxDecoration qui vous permet de faire cela facilement.

Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topRight,
      end: Alignment.bottomLeft,
      colors: [
        Colors.blue,
        Colors.red,
      ],
    )
  ),
  child: // [...]
)

Mais malheureusement, il n’y a pas d’équivalent sur le widget Text en Flutter. Il va donc falloir trouver une solution.

Introduction au ShaderMask

Pour pouvoir faire ce que nous souhaitons, je dois vous introduire un widget trop peu connu en Flutter, mais qui permet d’obtenir toutes sortes de résultats assez impressionnants: ShaderMask.

ShaderMask est un widget qui applique un masque généré par un shader à son enfant. — documentation officielle

Vous ne comprennez pas cette définition ? Ce n’est pas grave, nous allons tenter de l’expliquer.

Un Shader est un code exécuté par votre GPU. Dans l’immense majorité des cas, les shaders sont utilisés pour calculer le rendu des pixels affichés à votre écran. C’est quelque chose qui est très utilisé dans les jeux-vidéos mais aussi par Flutter, sans que vous ne vous en rendiez compte.

Un Masque est un calque qui permet de définir quelle partie de l’image sera affichée et quelle partie sera masquée, comme sur l’exemple ci-dessous.

Exemple de masque sur Lenna

Pour résumer, un ShaderMask va permet d’appliquer le résultat d’un shader sur ses enfants dans l’arbre.

Il est important de comprendre le rôle des différents paramètres du ShaderMask pour pouvoir s’en servir pleinement :

  • blendMode: Permet de choisir comment nous allons peindre sur la surface. Il existe beaucoup de blendMode possibles. Celui qui nous interesse est BlendMode.srcin qui permet d’afficher l’image source (ici le shader) et de se servir de l’image destination (ici les enfants) comme masque. Pour voir tous les modes disponibles, jetez un coup d’oeil à la documentation.
  • shaderCallback: Le shaderCallback est l’endroit où l’on va construire notre shader pour pouvoir s’en servir. Si j’avais design cette api, je l’aurais d’ailleurs probablement nommée shaderBuilder .
  • child: Si vous êtes fluent en Flutter, vous connaissez le paramètre, mais pour les autres, c’est ici que l’on va mettre le widget enfant dans l’arbre de widget. C’est la destination de notre shader.

Les Gradients

Il nous reste maintenant à trouver un moyen de faire un Shader de dégradé, d’assembler le tout, et le tour sera joué. En Flutter, il existe trois dégradés : LinearGradient, RadialGradient et SweepGradient.

Les trois dégradés possibles en Flutter

Ces trois dégradés héritent de la classe abstraite Gradient qui dispose d’une méthode Gradient.createShader(). La méthode prend en paramètre un rectangle correspondant à la zone d’affichage du shader. Attention de bien le mettre à la taille du widget cible et pas à la taille de l’écran. Cela donnerait un résultat décalé par rapport au résultat attendu.

ShaderMask + Gradient + Text = GradientText

Maintenant que nous avons trouvé les ingrédients pour notre recette de cuisine, il est temps de les assembler !

preview des trois éléments (shaderCallback, child, ShaderMask) indépendemment

import 'package:flutter/material.dart';

class GradientText extends StatelessWidget {
  final String data;
  final TextStyle? style;
  final Gradient gradient;
  final TextAlign? textAlign;

  const GradientText(this.data, {
    super.key,
    this.style,
    this.textAlign,
    required this.gradient
  });

  @override
  Widget build(BuildContext context) {
    return ShaderMask(
      blendMode: BlendMode.srcIn,
      shaderCallback: (bounds) => gradient.createShader(
        Rect.fromLTWH(0, 0, bounds.width, bounds.height),
      ),
      child: Text(data, style: style, textAlign: textAlign),
    );
  }
}

Et maintenant, il ne nous reste plus qu’à l’utiliser avec un joli dégradé multicolore ! 🌈

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GradientText("ama.",
        textAlign: TextAlign.center,
        style: GoogleFonts.rubik(
          fontWeight: FontWeight.w700,
          fontSize: 70
        ),
        gradient: const SweepGradient(colors: [
          Colors.red,
          Colors.pink,
          Colors.purple,
          Colors.deepPurple,
          Colors.deepPurple,
          Colors.indigo,
          Colors.blue,
          Colors.lightBlue,
          Colors.cyan,
          Colors.teal,
          Colors.green,
          Colors.lightGreen,
          Colors.lime,
          Colors.yellow,
          Colors.amber,
          Colors.orange,
          Colors.deepOrange,
        ])),
      ),
    );
  }
}

Et voilà, vous savez maintenant appliquer un dégradé dans du texte. Mais si vous avez bien compris la logique, il est possible d’appliquer des dégradés sur tous les éléments graphiques existants en Flutter ou même de créer d’autres types de Shaders. Maintenant que vous êtes arrivés au bout, je peux vous le dire, cet article était surtout une manière d’introduire l’utilisation des Shaders en Flutter et d’éveiller votre curiosité quant aux posibilités offertes par cela. Prenez le temps de jouer avec le ShaderMask et les Shaders, vous verrez que l’on peut faire des choses folles !