Bienvenue dans ce nouveau tuto Flutter en français, dans lequel nous allons coder la plus populaire des applications du moment: TikTok!

Nous allons recréer la page d’accueil de l’application TikTok avec Flutter et notamment la fameuse fonctionnalité de swipe.

L’application proposera plusieurs posts vidéos que l’utilisateur pourra swiper de bas en haut pour afficher de nouveaux contenus.

Voilà le résultat de l’application que nous allons réaliser aujourd’hui:

Tuto Flutter Français TikTok

Nous allons donc séparer ce tutorial Flutter en français en quatre parties:

  1. Création et configuration de l’application
  2. Design de la barre de navigation de TikTok
  3. Création de la fonctionnalité de Swipe vertical
  4. Design d’un post vidéo TikTok

N’hésitez pas également à télécharger le code source de l’application pour la tester rapidement sur un émulateur.

Flutter Tuto Français | 1. Création et configuration de l’application

Dans cette partie nous allons créer un projet Flutter et configurer les éléments principaux de notre application, comme les ressources et les packages que nous utiliserons par la suite.

1.1. Création du projet avec la commande Flutter

Ici nous allons tout simplement utiliser la commande Flutter permettant de créer un nouveau projet et ensuite préparer notre application.

Nous nous positionnons dans notre dossier de développement et nous ouvrons ensuite le terminal pour lancer la commande:

flutter create tiktok

Une fois que le projet est créé, nous pouvons l’ouvrir avec notre éditeur VS Code afin de le modifier.

On commence par se rendre dans le fichier principal main.dart et à supprimer tout son contenu, pour construire notre design en partant de zéro.

Cours Flutter Gratuit
Cours Flutter Gratuit

1.2 Importation des photos et des vidéos

Notre application aura besoin de vidéos de posts mises en avant dans notre application, nous devons donc ajouter ces vidéos dans le projet.

Pour cela, nous allons créer un dossier assets dans lequel nous allons mettre deux dossiers images et vidéos:

Tuto Flutter Français TikTok 1

Le dossier vidéos contiendra donc six vidéos que nous utiliserons dans notre application:

Tuto Flutter Français TikTok 2

De même que le dossier images contiendra toutes les images nécessaires au design de l’application:

Tuto Flutter Français TikTok 3

Nous pouvons ensuite nous rendre dans notre fichier pubspec.yaml afin de déclarer les adresses de nos dossiers comme ci-dessous :

assets:
  - assets/images/
  - assets/videos/

Nous entrons ensuite la commande suivante pour prendre en compte l’ajout des images et vidéos dans notre application :

flutter pub get

1.3 Installation des packages

Pour ce tutorial Flutter en français, nous allons utiliser les packages carousel_slider et video_player pour swiper nos vidéos:

Pour cela, nous devons ajouter les dépendances de nos packages dans le fichier pubspec.yaml comme ceci:

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.3
  video_player: ^2.2.4
  carousel_slider: ^4.0.0

Une fois les packages importés, enregistrez le fichier et le téléchargement devrait se faire automatiquement, sinon entrez la commande suivante:

flutter pub get

À la première ligne de notre fichier main.dart, nous allons importer le package Material qui nous donnera accès à tous les widgets et autres classes que Flutter propose.

Ensuite, nous pouvons importer les deux autres packages pour accéder aux fonctionnalités de swipe et de lecture vidéo.

Notre fichier main.dart ressemble maintenant à ceci :

import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:video_player/video_player.dart';

Nos deux packages de base étant importés, nous allons commencer à construire notre application Flutter.

1.4 Déclaration de la fonction main() et de la classe MyApp

Tout d’abord, nous allons commencer par déclarer la fonction main() dans notre fichier main.dart.

C’est elle qui lancera le code de notre application, grâce à la fonction runApp() qui chargera la classe MyApp:

void main() {
  runApp(const MyApp());
}

La classe MyApp va contenir la méthode build() qui permettra de dessiner l’interface utilisateur avec le widget MaterialApp() (application utilisant le material design).

Nous donnons un titre à notre application avec le champ title, et nous mettons également l’attribut debugShowCheckedModeBanner à false pour supprimer la bannière de débogage.

Enfin nous avons le champ home qui va nous permettre de définir le Widget qui sera la page d’accueil de l’application.

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter TikTok',
      debugShowCheckedModeBanner: false,
      home: MyStatefulWidget(),
    );
  }
}

Ici nous appelons le widget MyStatefulWidget() qui représentera notre page d’accueil que nous allons créer dans la prochaine partie.

Flutter Révolution

Flutter Tuto Français | 2. Design de la barre de navigation TikTok

Maintenant que notre application est créée et que nous avons configuré les packages et les ressources de l’application, nous pouvons passer à l’étape suivante.

Il s’agit de reproduire le menu de navigation de TikTok en reprenant le même thème couleur et l’icône spécifique à l’application.

Pour cela nous allons utiliser le widget BottomNavigationBar qui permet de créer une barre de navigation Flutter très facilement.

La classe BottomNavigationBar est le widget officiel de Flutter pour réaliser une barre de navigation, dont je vous laisse la documentation: https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html

Ils nous proposent d’ailleurs comme exemple, un premier StatefulWidget de démonstration avec le code suivant:

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _selectedIndex = 0;
  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
    Text(
      'Index 2: School',
      style: optionStyle,
    ),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomNavigationBar Sample'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Business',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            label: 'School',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

Ce premier exemple permet de créer une application de navigation très simple, avec différents onglets en bas de la page.

Il faut bien comprendre le principe de cet exemple, avec notamment le fait que c’est bien un widget Scaffold() qui est renvoyé.

Le Scaffold() ou échafaudage en français permet de créer tous les éléments de base d’une page, comme la appBar, un bouton flottant ou encore une bottomNavigationBar.

La technique utilisée par l’exemple précédent consiste à changer le contenu du corps de la page à chaque clique sur un élément de la barre de navigation.

On repère ici l’indice de l’onglet et on change en fonction le widget renvoyé à partir de la liste _widgetOptions.

Dans notre cas, nous n’utiliserons pas cette fonctionnalité de “navigation” ou de changement d’affichage, puisque nous ne travaillerons que sur la page d’accueil du menu tiktok.

Par défaut on propose donc, un simple Container() dans le champ body de notre Scaffold():

body: Container(color: Colors.white),

Ensuite on peut travailler sur tout le thème couleur de notre BottomNavigationBar(), en ajoutant certains champs et en précisant les couleurs de notre choix:

bottomNavigationBar: BottomNavigationBar(
  type: BottomNavigationBarType.fixed,
  backgroundColor: Color(0xFF141518),
  showSelectedLabels: false,
  showUnselectedLabels: false,
  unselectedItemColor: Colors.grey,
  selectedItemColor: Colors.white,

Pour les différents onglets de notre menu, on peut modifier le champ label même s’ils ne seront pas affichés et surtout l’icône de chaque item:

BottomNavigationBarItem(
  icon: Icon(Icons.home),
  label: 'Home',
),

Dans l’application TikTok, l’onglet du milieu est constitué par une icône différente, qui dans notre cas peut être représenté par une image en PNG.

Tuto Flutter Français TikTok Icon

Pour afficher un item de ma BottomNavigationBarItem() avec une image PNG, je peux utiliser le widget Image dans le champ icon directement:

BottomNavigationBarItem(
  icon: Image.asset(
    'assets/images/tiktok_add.png',
    height: 25,
  ),
  label: 'Add',
),

Voilà donc pour les principales modifications à apporter à notre BottomNavigationBar() mais aussi à notre MyStatefulWidget() de la documentation Flutter.

Je vous laisse le code complet de ce widget que j’ai simplifié en supprimant la liste de widget par exemple:

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);
  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _selectedIndex = 0;
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFF141518),
      body: Container(color: Colors.white),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        backgroundColor: Color(0xFF141518),
        showSelectedLabels: false,
        showUnselectedLabels: false,
        unselectedItemColor: Colors.grey,
        selectedItemColor: Colors.white,
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Image.asset(
              'assets/images/tiktok_add.png',
              height: 25,
            ),
            label: 'Add',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.comment_outlined),
            label: 'Comment',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person_outline),
            label: 'Profil',
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
      ),
    );
  }
}

Vous pouvez ensuite le modifier et l’adapter à votre projet si besoin pour notamment remettre la fonctionnalité de navigation.

Flutter Tuto Français | 3. Créer de la fonctionnalité de Swipe vertical

On continue ce tuto flutter français, en créant la fonctionnalité de swipe si connue dans l’application TikTok.

Pour créer cette fonctionnalité, nous utiliserons le package carousel_slider dont je vous laisse la documentation: https://pub.dev/packages/carousel_slider

Ce package nous permet de créer toute sorte de slider ou de carousel, pour afficher n’importe quel élément à swiper dans une application.

Le widget disponible avec le package est directement le widget CarouselSlider() que nous allons pouvoir personnaliser.

La documentation du package propose d’ailleurs un premier exemple très simple pour découvrir le fonctionnement du widget:

CarouselSlider(
  options: CarouselOptions(height: 400.0),
  items: [1,2,3,4,5].map((i) {
    return Builder(
      builder: (BuildContext context) {
        return Container(
          width: MediaQuery.of(context).size.width,
          margin: EdgeInsets.symmetric(horizontal: 5.0),
          decoration: BoxDecoration(
            color: Colors.amber
          ),
          child: Text('text $i', style: TextStyle(fontSize: 16.0),)
        );
      },
    );
  }).toList(),
)

Vous pouvez tester ce code en l’ajoutant dans le champ body de notre MyStatefulWidget précédent, ou en créant un nouveau widget complet.

Vous pouvez donc comme moi créer un nouveau StatelessWidget que vous pouvez appeler HomePage.

À l’intérieur ce celui-ci, vous pouvez donc retourner le widget CarouselSlider() pour le visualiser dans votre application.

Dans cet exemple, il parcourt directement une liste de chiffre pour afficher ses différents indices: [1,2,3,4,5].

Dans notre cas, nous allons utiliser et déclarer plus haute une liste de Map (ou d’objet) pour contenir différentes informations sur nos boîtes.

Pour commencer nous allons contenir un indice différent pour chaque boîte, mais également une couleur différente pour pouvoir les distinguer:

final List<Map> tiktokItems = [
  {
    "title": "1",
    "color": Colors.blue,
  },
  {
    "title": "2",
    "color": Colors.green,
  },
  {
    "title": "3",
    "color": Colors.red,
  },
];

De cette manière, on peut reprendre cette liste et la parcourir à nouveau pour afficher chaque Container() avec une couleur différente.

On fait également en sorte que la boîte prenne toute la largeur de la page et que le swipe se fasse à la verticale:

CarouselSlider(
  options: CarouselOptions(
    enableInfiniteScroll: false,
    height: double.infinity,
    scrollDirection: Axis.vertical,
    viewportFraction: 1.0,
  ),
  items: tiktokItems.map((item) {
    return Builder(
      builder: (BuildContext context) {
        return Container(
          color: item['color'],
          child: Center(
            child: Text('text ' + item['title']),
          ),
        );
      },
    );
  }).toList(),
);

Avec ce simple petit code et surtout grâce au package carousel_slider, nous sommes capables de reproduire l’animation de swipe de tiktok.

J’ai donc personnellement inclus tout ce code dans le widget HomePage, entre la déclaration de la liste de données et du widget CarouselSlider():

class HomePage extends StatelessWidget {
  HomePage({Key? key}) : super(key: key);
  final List<Map> tiktokItems = [
    {
      "title": "1",
      "color": Colors.blue,
    },
    {
      "title": "2",
      "color": Colors.green,
    },
    {
      "title": "3",
      "color": Colors.red,
    },
  ];

  @override
  Widget build(BuildContext context) {
    return CarouselSlider(
      options: CarouselOptions(
        enableInfiniteScroll: false,
        height: double.infinity,
        scrollDirection: Axis.vertical,
        viewportFraction: 1.0,
      ),
      items: tiktokItems.map((item) {
        return Builder(
          builder: (BuildContext context) {
            return Container(
              color: item['color'],
              child: Center(
                child: Text('text ' + item['title']),
              ),
            );
          },
        );
      }).toList(),
    );
  }
}

Voilà donc pour la fonctionnalité de swipe à intégrer dans notre application tiktok flutter.

Flutter Tuto Français | 4. Design d’un post vidéo TikTok

On termine donc notre tuto flutter français par le design de post complet de TikTok, de la lecture vidéo en fond à la disposition de tous les éléments.

Nous aurons en effet besoin de superposer une vidéo qui se jouera en fond de toutes les informations du post, avec la description, etc.

On commence donc par la possibilité de jouer une vidéo en fond de notre page et différente pour chaque post.

On commence donc par modifier la liste tiktokItems que nous avons créé pour la fonctionnalité de swipe.

Simplement ici, nous n’aurons besoin que de l’adresse des différents fichiers vidéos de notre application.

On peut donc créer différentes Map, avec à chaque fois le champ “video” et l’adresse précise de la vidéo que nous souhaitons charger:

final List<Map> tiktokItems = [
  {
    "video": "assets/videos/video_1.mp4",
  },
  {
    "video": "assets/videos/video_2.mp4",
  },
  {
    "video": "assets/videos/video_3.mp4",
  },
  {
    "video": "assets/videos/video_4.mp4",
  },
  {
    "video": "assets/videos/video_5.mp4",
  },
  {
    "video": "assets/videos/video_6.mp4",
  },
];

Nous allons ensuite pouvoir parcourir cette liste et renvoyer par la suite le champ “video” à un autre widget que nous allons créer:

VideoWidget(
  videoUrl: item['video'],
),

Ce VideoWidget prendra comme seul paramètre l’adresse de la vidéo à charger et nous permettra de gérer son comportement de manière détachée.

Vous pouvez dors et déjà mettre à jour votre widget HomePage() avec la nouvelle liste et le nouveau CarouselSlider avec à l’intérieur le widget Stack():

class HomePage extends StatelessWidget {
  HomePage({Key? key}) : super(key: key);
  final List<Map> tiktokItems = [
    {
      "video": "assets/videos/video_1.mp4",
    },
    {
      "video": "assets/videos/video_2.mp4",
    },
    {
      "video": "assets/videos/video_3.mp4",
    },
    {
      "video": "assets/videos/video_4.mp4",
    },
    {
      "video": "assets/videos/video_5.mp4",
    },
    {
      "video": "assets/videos/video_6.mp4",
    },
  ];

  @override
  Widget build(BuildContext context) {
    return CarouselSlider(
      options: CarouselOptions(
        enableInfiniteScroll: false,
        height: double.infinity,
        scrollDirection: Axis.vertical,
        viewportFraction: 1.0,
      ),
      items: tiktokItems.map((item) {
        return Builder(
          builder: (BuildContext context) {
            return Container(
              color: Color(0xFF141518),
              child: Stack(
                children: [
                  VideoWidget(
                    videoUrl: item['video'],
                  ),
                ],
              ),
            );
          },
        );
      }).toList(),
    );
  }
}

C’est le widget Stack() qui nous permet de superposer notre vidéo et les informations de la vidéo.

Nous allons donc maintenant pouvoir nous concentrer sur la fonctionnalité de lecture vidéo, que nous détaillons dans le VideoWidget.

Pour commencer, je vous redonne la documentation du package video_player: https://pub.dev/packages/video_player

Ce package nous fournit le widget VideoPlayer() qui a besoin de prendre en paramètre un simple controller que nous allons détailler:

VideoPlayer(_controller);

C’est à ce controller, que nous allons préciser toutes les propriétés de notre vidéo, à commencer par sa source.

Vous pouvez commencer par déclarer le controller suivant avec comme source la vidéo locale suivante:

_controller = VideoPlayerController.asset('assets/videos/video_1.mp4');

Mais pour le fonctionnement de notre application, nous allons transmettre le paramètre videoUrl et l’indiquer en paramètre dans le controller justement:

_controller = VideoPlayerController.asset(videoUrl);

Voilà le premier code du VideoWidget(), pour simplement tester le bon fonctionnement du package et de l’affichage des différentes vidéos:

class VideoWidget extends StatefulWidget {
  VideoWidget({Key? key, required this.videoUrl}) : super(key: key);
  final String videoUrl;
  @override
  _VideoWidgetState createState() => _VideoWidgetState(this.videoUrl);
}

class _VideoWidgetState extends State<VideoWidget> {
  final String videoUrl;
  late VideoPlayerController _controller;

  _VideoWidgetState(this.videoUrl);

  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.asset(videoUrl)
      ..initialize().then((_) {
        setState(() {});
      });
  }

  @override
  Widget build(BuildContext context) {
    return VideoPlayer(_controller);
  }
}

Attention, ce code par défaut ne lance pas la lecture de nos vidéos dans l’application, pour cela vous devez vous rendre dans la méthode initState().

Vous devez lancer la lecture vidéo une fois que le controller a été initialisé:

void initState() {
  super.initState();
  _controller = VideoPlayerController.asset(videoUrl)
    ..initialize().then((_) {
    _controller.setLooping(true); // à ajouter pour la lecture en boucle
    _controller.play(); // pour lancer la lecture vidéo
    setState(() {});
  });
}

Voilà donc pour la partie lecture vidéo, dont le widget se trouve toujours dans le widget Stack() accompagné d’un autre widget désormais.

Stack(
  children: [
    VideoWidget(
      videoUrl: item['video'],
    ),
    PostContent(),
  ],
),

Ce widget PostContent est celui qui va contenir toutes les informations de notre post TikTok, à commencer par la disposition des différents éléments.

Comme détaillée étape par étape dans la vidéo, voilà le premier rendu du widget PostContent, pour visualiser les différentes parties en couleur:

class PostContent extends StatelessWidget {
  const PostContent({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          color: Colors.blue,
          height: 100,
        ),
        Expanded(
          child: Container(
            color: Colors.red,
            child: Row(
              children: [
                Expanded(
                  child: Container(
                    color: Colors.green,
                  ),
                ),
                Container(
                  width: 80,
                  color: Colors.red,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      Container(
                        padding: EdgeInsets.all(10),
                        height: 80,
                        color: Colors.blue,
                      ),
                      Container(
                        height: 80,
                        color: Colors.teal,
                      ),
                      Container(
                        height: 80,
                        color: Colors.yellow,
                      ),
                      Container(
                        height: 80,
                        color: Colors.orange,
                      ),
                      Container(
                        height: 80,
                        color: Colors.purple,
                      ),
                    ],
                  ),
                )
              ],
            ),
          ),
        )
      ],
    );
  }
}

Je vous propose ensuite le même widget, mais avec toutes les informations détaillées, comme les différents textes, icônes et images:

class PostContent extends StatelessWidget {
  const PostContent({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          height: 100,
          padding: EdgeInsets.only(top: 40),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'Following',
                style: TextStyle(
                  color: Colors.white54,
                  fontWeight: FontWeight.w600,
                ),
              ),
              SizedBox(width: 20),
              Text(
                'For you',
                style: TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ],
          ),
        ),
        Expanded(
          child: Container(
            child: Row(
              children: [
                Expanded(
                  child: Container(
                    padding: EdgeInsets.all(10),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.end,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          '@extremesports_95',
                          style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.w600,
                          ),
                        ),
                        SizedBox(height: 10),
                        Text(
                          'Goein full send in Squaw Valley. #snow @snowboarding # extremesports #sendit #winter',
                          style: TextStyle(
                            color: Colors.white,
                          ),
                        ),
                        SizedBox(height: 10),
                        Row(
                          children: [
                            Icon(
                              Icons.music_note,
                              color: Colors.white,
                              size: 15,
                            ),
                            SizedBox(width: 5),
                            Text(
                              'Original Sound - extremesports_95',
                              style: TextStyle(
                                color: Colors.white,
                              ),
                            ),
                          ],
                        )
                      ],
                    ),
                  ),
                ),
                Container(
                  width: 80,
                  padding: EdgeInsets.only(bottom: 10),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      Container(
                        padding: EdgeInsets.all(10),
                        child: Stack(
                          alignment: AlignmentDirectional.bottomCenter,
                          children: [
                            Container(
                              margin: EdgeInsets.only(bottom: 10),
                              child: CircleAvatar(
                                radius: 25,
                                backgroundImage: AssetImage(
                                  'assets/images/photo-5.jpeg',
                                ),
                              ),
                            ),
                            Container(
                              padding: EdgeInsets.all(2),
                              decoration: BoxDecoration(
                                color: Colors.red,
                                borderRadius: BorderRadius.circular(20),
                              ),
                              child: Icon(
                                Icons.add,
                                color: Colors.white,
                                size: 15,
                              ),
                            )
                          ],
                        ),
                      ),
                      Container(
                        height: 80,
                        child: Column(
                          children: [
                            Icon(
                              Icons.favorite,
                              color: Colors.white.withOpacity(0.85),
                              size: 45,
                            ),
                            Text(
                              '25.0K',
                              style: TextStyle(
                                color: Colors.white,
                                fontSize: 12,
                                fontWeight: FontWeight.w600,
                              ),
                            )
                          ],
                        ),
                      ),
                      Container(
                        height: 80,
                        child: Column(
                          children: [
                            Icon(
                              Icons.comment,
                              color: Colors.white.withOpacity(0.85),
                              size: 45,
                            ),
                            Text(
                              '156',
                              style: TextStyle(
                                color: Colors.white,
                                fontSize: 12,
                                fontWeight: FontWeight.w600,
                              ),
                            )
                          ],
                        ),
                      ),
                      Container(
                        height: 80,
                        child: Column(
                          children: [
                            Icon(
                              Icons.share,
                              color: Colors.white.withOpacity(0.85),
                              size: 45,
                            ),
                            Text(
                              '123',
                              style: TextStyle(
                                color: Colors.white,
                                fontSize: 12,
                                fontWeight: FontWeight.w600,
                              ),
                            )
                          ],
                        ),
                      ),
                      AnimatedLogo(),
                    ],
                  ),
                )
              ],
            ),
          ),
        )
      ],
    );
  }
}

La seule partie à détailler de ce PostContent, concerne le logo animé de TikTok en bas à droite de l’écran.

Nous allons de voir créer StatefulWidget de manière séparée pour gérer son comportement dynamique.

Pour commencer, vous aurez besoin de la bibliothèque Dart Math pour accéder à la variable Pi et ainsi créer une animation de rotation.

Vous pouvez donc importer cette librairie dans votre fichier Dart:

import 'dart:math' as math;

Vous serez ensuite capable d’effectuer une rotation à l’aide du widget Transform et de la valeur du champ angle qui varie:

Transform.rotate(
  angle: _controller.value * 2 * math.pi,
  child: child,
);

Voilà le code complet du widget AnimatedLogo() avec toute la gestion de l’animation et du contenu à animer (deux images l’une dans l’autre):

class AnimatedLogo extends StatefulWidget {
  AnimatedLogo({Key? key}) : super(key: key);

  @override
  _AnimatedLogoState createState() => _AnimatedLogoState();
}

class _AnimatedLogoState extends State<AnimatedLogo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(
      duration: const Duration(milliseconds: 4000),
      vsync: this,
    );
    _controller.repeat();
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (_, child) {
        return Transform.rotate(
          angle: _controller.value * 2 * math.pi,
          child: child,
        );
      },
      child: Container(
        height: 45,
        width: 45,
        padding: EdgeInsets.all(5),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(25),
          image: DecorationImage(
            image: AssetImage("assets/images/disc_icon.png"),
          ),
        ),
        child: Image.asset('assets/images/tiktok_icon.png'),
      ),
    );
  }
}

Voilà donc qui conclut notre tuto flutter français sur la création de l’application TikTok.

C’est le cinquième d’une longue série qui vient compléter la formation Flutter Révolution avec du contenu plus orienté sur le design pratique avec Flutter.

C’est aussi l’occasion pour un plus grand nombre de personnes de découvrir Flutter et de tester en direct son fonctionnement.

Si ce n’est pas déjà fait, commencez le cours gratuit Flutter pour installer Flutter et le configurer sur votre ordinateur.

Rejoignez le cours complet Flutter Révolution si vous souhaitez créer des applications complètes avec Firebase, Google Maps et Stripe.